Giter Site home page Giter Site logo

i18n's Introduction

Nano Stores I18n

Tiny and flexible JS library to make your web application translatable. Uses Nano Stores state manager and JS Internationalization API.

  • Small. Between 448 and 844 bytes (minified and gzipped). Zero dependencies.
  • Works with React, Preact, Vue, Svelte, and plain JS.
  • Supports tree-shaking and translation on-demand download.
  • Plain JSON translations compatible with online translation services like Weblate.
  • Out of the box TypeScript support for translations.
  • Flexible variable translations. You can change translation, for instance, depends on screen size.
// components/post.jsx
import { params, count } from '@nanostores/i18n' // You can use own functions
import { useStore } from '@nanostores/react'
import { i18n, format } from '../stores/i18n.js'

export const messages = i18n('post', {
  title: 'Post details',
  published: params<{ at: string }>('Was published at {at}')
  posts: count({
    one: '{count} comment',
    many: '{count} comments'
  })
})

export const Post = ({ author, comments, publishedAt }) => {
  const t = useStore(messages)
  const { time } = useStore(format)
  return <article>
    <h1>{t.title}</h1>
    <p>{t.published({ at: time(publishedAt) })}</p>
    <p>{t.comments(comments.length)}</p>
  </article>
}
// stores/i18n.js
import { createI18n, localeFrom, browser, formatter } from '@nanostores/i18n'
import localeSettings from './locale-settings.js'

export const locale = localeFrom(
  localeSettings,                            // User’s locale from localStorage
  browser({ available: ['en', 'fr', 'ru'] }) // or browser’s locale auto-detect
)

export const format = formatter(locale)

export const i18n = createI18n(locale, {
  get (code) {
    return fetchJSON(`/translations/${code}.json`)
  }
})
// public/translations/ru.json
{
  "post": {
    "title": "Данные о публикации",
    "published": "Опубликован {at}",
    "comments": {
      "one": "{count} комментарий",
      "few": "{count} комментария",
      "many": "{count} комментариев",
    }
  },
  // Translations for all other components
}
Sponsored by Evil Martians

Install

npm install nanostores @nanostores/i18n

Usage

We store locale, time/number formatting functions and translations in Nano Stores’ atoms. See Nano Stores docs to learn how to use atoms in your framework.

Locale

Locale is a code of user’s language and dialect like hi (Hindi), de-AT (German as used in Austria). We use Intl locale format.

Current locale should be stored in store. We have localeFrom() store builder to find user’s locale in first available source:

import { localFrom } from '@nanostores/i18n'

export const locale = localeFrom(store1, store2, store3)

We have store with a locale from browser settings. You need to pass list of available translations of your application. If store will not find common locale, it will use fallback locale (en, but can be changed by fallback option).

import { localeFrom, browser } from '@nanostores/i18n'

export const locale = localeFrom(
  ,
  browser({ available: ['en', 'fr', 'ru'] })
)

Before browser store, you can put a store, which will allow user to override locale manually. For instance, you can keep an locale’s override in localStorage.

import { persistentAtom } from '@nanostores/persistent'

export const localeSettings = persistentAtom<string>('locale')

export const locale = localeFrom(
  localeSettings,
  browser({ available: ['en', 'fr', 'ru'] })
)

Or you can take user’s locale from URL router:

import { computed } from 'nanostores'
import { router } from './router.js'

const urlLocale = computed(router, page => page?.params.locale)

export const locale = localeFrom(
  urlLocale,
  browser({ available: ['en', 'fr', 'ru'] })
)

You can use locale as any Nano Store:

import { useStore } from '@nanostores/react'
import { locale } from '../stores/i18n.js'

// Pure JS example
locale.listen(code => {
  console.log(`Locale was changed to ${code}`)
})

// React example
export const CurrentLocale = () => {
  let code = useStore(locale)
  return `Your current locale: ${code}`
}

For tests you can use simple atom:

import { atom } from 'nanostores'

const locale = atom('en')
locale.set('fr')

Date & Number Format

formatter() creates a store with a functions to format number and time.

import { formatter } from '@nanostores/i18n'

export const format = formatter(locale)

This store will have time() and number() functions.

import { useStore } from '@nanostores/react'
import { format } from '../stores/i18n.js'

export const Date = (date) => {
  let { time } = useStore(format)
  return time(date)
}

These functions accepts options of Intl.DateTimeFormat and Intl.NumberFormat.

time(date, {
  hour12: false,
  month: 'long',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric'
}) //=> "November 1, 01:56:33"

I18n Object

I18n objects is used to define new component and download translations on locale changes.

import { createI18n } from '@nanostores/i18n'

export const i18n = createI18n(locale, {
  async get (code) {
    return await fetchJSON(`/translations/${code}.json`)
  }
})

In every component you will have base translation with functions and types. This translation will not be download from the server. By default, you should use English. You can change base locale in components with baseLocale option.

Translations

We have 2 types of translations:

Base translation. Developers write it in component sources. It is used for TypeScript types and translation functions (count(), params(), etc).

export const messages = i18n('post', {
  title: 'Post details',
  published: params<{ at: string }>('Was published at {at}')
  posts: count({
    one: '{count} comment',
    many: '{count} comments'
  })
})

Other translations They use JSON format and will be created by translators.

{
  "post": {
    "title": "Данные о публикации",
    "published": "Опубликован {at}",
    "comments": {
      "one": "{count} комментарий",
      "few": "{count} комментария",
      "many": "{count} комментариев",
    }
  }
}

Parameters

params() translation transform replaces parameters in translation string.

import { useStore } from '@nanostores/react'
import { params } from '@nanostores/i18n'
import { i18n } from '../stores/i18n.js'

export const messages = i18n('hi', {
  hello: params<{ name: string }>('Hello, {name}')
})

export const Robots = ({ name }) => {
  const t = useStore(messages)
  return t.hello({ name })
}

You can use time() and number() formatting functions.

Pluralization

In many languages, text could be different depends on items count. Compare 1 robot/2 robots in English with 1 робот/2 робота/3 робота/21 робот in Russian.

We hide this complexity with count() translation transform:

import { useStore } from '@nanostores/react'
import { count } from '@nanostores/i18n'
import { i18n } from '../stores/i18n.js'

export const messages = i18n('robots', {
  howMany: count({
    one: '{count} robot',
    many: '{count} robots'
  })
})

export const Robots = ({ robots }) => {
  const t = useStore(messages)
  return t.howMany(robots.length)
}
{
  "robots": {
    "howMany": {
      "one": "{count} робот",
      "few": "{count} робота",
      "many": "{count} роботов",
    }
  }
}

count() uses Intl.PluralRules to get pluralization rules for each locale.

Custom Variable Translations

In additional to params() and count() you can define your own translation transforms. Or you can change pluralization or parameters syntax by replacing count() and params().

import { transform, strings } from '@nanostores/i18n'

// Add parameters syntax like hello: "Hi, %1"
export const paramsList = transform((locale, translation, ...args) => {
  return strings(translation, str => {
    return str.replace(/%\d/g, pattern => args[pattern.slice(1)])
  })
})
import { paramsList } from '../lib/paramsList.ts'

export const messages = i18n('hi', {
  hello: paramsList('Hello, %1')
})

Translation Process

The good I18n support is not about the I18n library, but about translation process.

  1. Developer creates base translation in component’s source.

  2. CI runs script to extract base translation to JSON.

    import { messagesToJSON } from '@nanostores/i18n'
    
    const files = await glob('src/*.tsx')
    const components = await Promise.all(files.map(file => {
      return (await import(file).messages)
    }))
    const json = messagesToJSON(...components)
  3. CI uploads JSON with base translation to online translation service.

  4. Translators translate application on this service.

  5. CI or translation service download translation JSONs to the project.

Server-Side Rendering

For SSR you may want to use own locale store and custom i18n instance with another way to load translations.

import { createI18n } from '@nanostores/i18n'
import { atom } from 'nanostores'

let locale, i18n

if (isServer) {
  locale = atom(db.getUser(userId).locale || parseHttpLocaleHeader())
  i18n = createI18n(locale, {
    async get (code) {
      return JSON.parse(await readFile(`public/translations/${code}.json`))
    }
  })
} else {
  
}

export { locale, i18n }

You may need to wait for translation loading before rendering the HTML.

if (i18n.loading.get()) {
  await new Promise(resolve => {
    let unbind = i18n.loading.listen(loading => {
      if (!loading) {
        resolve()
        unbind()
      }
    })
  })
}
const html = ReactDOMServer.renderToString(<App />)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.