npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

astro-nanointl

v1.0.0

Published

Tiny set of tools to implement internationalization for Astro

Downloads

99

Readme

Mask Group 9

Tiny, yet powerful set of tools to integrate internationalization (a.k.a i18n, a.k.a intl) to your Astro project. Strategy-agnostic (supports both SSG and SSR) and inspired by nanostores/i18n.

Before you use

These tool are not supposed to be used on the client. Even though they will work, there might be inconsistencies between the browsers as tools heavily rely on JavaScript Intl.

Therefore, you should be careful when using these tools on the client. Keep in mind that some older browsers might not support each and every feature.

Install

Opt in by installing astro-nanointl into your Astro project. Use your favorite package manager to install it from npm:

npm install -D astro-nanointl
pnpm add -D astro-nanointl

Plug

Next, it is recommended to set up i18n Routing.

Note! The package itself is not doing anything with routing, it is just adds the abstraction layer over your translations to streamline the localization process.

The simplest way to enable i18n Routing is as follows:

import { defineConfig } from 'astro/config'

export default defineConfig({
+  i18n: {
+    defaultLocale: 'en',
+    locales: ['en', 'ru']
+  },
})

Play

Now you're ready to give astro-nanointl a try! Let's explore what we can do with it, shall we?

Generate/render pages

We are unopinionated on how you should generate/render pages. You could create pages explicitly (as shown in Astro docs) or use file-based routing to do the trick.

Current locale

In order to retrieve the current locale you should use built-in currentLocale property from Astro global.

Simple as that:

const locale = Astro.currentLocale

Translations

You might be wondered: where do I store my translations? The answer is anywhere you like. Yeah, you can store your translations in file system, utilize Content Collections or even fetch them from remote source.

Sounds awesome, right? However, here are few points you should remember:

  • Translations are strings, the only exception is count transformer (see pluralization)
  • You should not include your default language translations as they are already inside your source code
  • The translations must match the schema you declared inside your source code, otherwise they won't be used
  • Alongside with your translations you must provide the locale they are created for, which should be comparable with the one JavaScript Intl can use

Use Translations

Use the useTranslations function to start localization:

import { useTranslations } from "astro-nanointl"

const locale = Astro.currentLocale!
const translations = await getTranslations(`${locale}/index`)

const t = useTranslations({
  hello: 'world',
  stars: count({
    one: '{count} star',
    many: '{count} stars'
  })
}, {
  data: translations?.data
  locale
})

From the example you can see that useTranslations function takes two arguments, both of them are objects.

First argument is the translations schema. The schema represents your default language translations and is used to seamlessly match your other translations against it. While declaring the schema you can also use transformers (see Parameterization, Pluralization and more) to add some features while localizing.

{
  hello: 'world',

  // with transformer
  stars: count({
    one: '{count} star',
    many: '{count} stars'
  })
}

Second argument is translations themselves. This is the object containing two properties: data and locale:

  • The data property should contain the translations you can load from anywhere you want but must match the translations schema, otherwise the default language translations will be used. The data property is not optional, but it accepts undefined as a value, which means that no translations exist and the default language translations should be used.

  • The locale property must contain the locale for which translations were created for. It also must be compatible with JavaScript Intl spec.

{
  data: translations?.data // can be `undefined`
  locale
}

To wrap things up:

  • useTranslations requires two arguments:
    • Translations schema:
    • Translations:
      • data:
        • contains translations to use
        • must match the translations schema
        • accepts undefined as a value, which means that no translations exist and the default language translations should be used
      • locale contains the locale for which translations were created for

Usage with Content Collections

If you decided to store translations using Content Collections, we have a great news for you: we export the translationsSchema to help you define your collection schema!

config.ts file inside src/content directory:

import { defineCollection } from "astro:content"
import { translationsSchema } from "astro-nanointl"

export const collections = {
  translations: defineCollection({
    type: 'data',
    schema: translationsSchema
  })
}

Folder structure:

src/
  content/
    translations/
      ru/
        index.yaml
        some-page.yaml
        some-component.yaml
    config.ts
  pages/...

ru/index.yaml inside src/content/translations:

hello: мир

stars:
  one: {count} звезда
  few: {count} звезды
  many: {count} звезд

index.astro file inside src/pages directory:

---
import { getEntry } from "astro:content"
import { useTranslations } from "astro-nanointl"
import { count } from "astro-nanointl/transformers"

// page render omitted

const locale = Astro.currentLocale!
const translations = await getEntry('translations', `${locale}/index`)

const t = useTranslations({
  hello: 'world',
  stars: count({
    one: '{count} star',
    many: '{count} stars'
  })
}, {
  data: translations?.data
  locale
})
---

<p>{ t.hello }</p>
<p>{ t.stars(10) }</p>

Usage with file system

If you feel comfortable with storing your translations as files and manage them with something like i18n-ally, this example usage is for you!

Folder structure:

src/
  locales/
    ru.json
  pages/...

ru.json inside src/locales:

{
  "hello": "мир",
  "stars": {
    "one": "{count} звезда",
    "few": "{count} звезды",
    "many": "{count} звезд"
  }
}

Note! If you would like to use yaml or yml you might want to enable YAML in Astro manually.

index.astro file inside src/pages directory:

---
import { useTranslations } from "astro-nanointl"
import { count } from "astro-nanointl/transformers"

// page render omitted

const locale = Astro.currentLocale!
const translations = await import(`../locales/${locale}.json`)

const t = useTranslations({
  hello: 'world',
  stars: count({
    one: '{count} star',
    many: '{count} stars'
  })
}, {
  data: translations.default
  locale
})
---

<p>{ t.hello }</p>
<p>{ t.stars(10) }</p>

Usage with remote source

You could use fetch to load your translations from remote source. All the other steps will look the same!

index.astro file inside src/pages directory:

---
import { useTranslations } from "astro-nanointl"
import { count } from "astro-nanointl/transformers"

// page render omitted

const locale = Astro.currentLocale!

const response = await fetch(`https://example.com/api/${locale}/index-page`)
const translations = await response.json()

const t = useTranslations({
  hello: 'world',
  stars: count({
    one: '{count} star',
    many: '{count} stars'
  })
}, {
  data: translations
  locale
})
---

<p>{ t.hello }</p>
<p>{ t.stars(10) }</p>

Parameterization, Pluralization and more

You can use several transformers that will help you to enhance your developer experience while localizing.

Note! The transformers are imported from astro-nanointl/transformers

params

Particularly useful when you have an object or many different parameters that need to be included in the translation.

const t = useTranslations({
  greeting: params('Good {timeOfDay}, {username}!'),
}, translations)

t.greeting({ timeOfDay: 'morning', name: '@eskozloi' }) // prints `Good morning, @eskozloi!`

count

Use it when you need to introduce pluralization.

const t = useTranslations({
  stars: count({
    one: 'a star',
    many: '{count} stars'
  })
}, translations)

t.count(1) // prints `a star`
t.count(3) // prints `3 stars`

Tip! If you also want to interpolate a number into translation string, add {count}

args

Useful when you just need to interpolate some values into translation string. The limit is 9 arguments.

const t = useTranslations({
  myNameIs: args('Hey, my name is %1')
}, translations)

t.myNameIs('John') // prints `Hey, my name is John`

format

Although it is not a transformer, you can use format to format dates, numbers etc. It is just an abstraction for JavaScript Intl to simplify its use. You can always replace it with default Intl implementation if you want.

import { format } from 'astro-nanointl'

const yesterday = format(locale).time(-1, 'day') // contains `1 day ago`

Type safety

All the transformers provide type safety but keep in mind that your schema should be a const. This means that if you don't create your schema object when you call useTranslations function, you may need to add as const to your schema object to preserve transformer types.

const schema = {
  hello: 'world',
  stars: count({
    one: 'a star',
    many: '{count} stars'
  })
} as const

const t = useTranslations(schema, ...)