{ } Lips v0.2.0

Internationalization

i18n is built in. Mark the text you want translated, register dictionaries, and switch language at runtime — every marked binding re-renders.

Dictionaries

const lips = new Lips()

lips.i18n.setDictionary('en', {
  'Welcome': 'Welcome',
  'Submit': 'Submit'
})

lips.i18n.setDictionary('fr', {
  'Welcome': 'Bienvenue',
  'Submit': 'Soumettre'
})

lips.i18n.setDictionary('es', {
  'Welcome': 'Bienvenido',
  'Submit': 'Enviar'
})

Dictionary ids are the language part of a locale: fr covers fr, fr-FR and fr-CA. The default language is the browser’s navigator.language.

Switching language

lips.setLanguage('fr')
lips.setLanguage('en-GB')

lips.getLanguage()   // → 'en-GB'

Every binding marked for translation re-runs on change — there is nothing to refresh manually.

Marking content

Add i18n to an element to translate its text:

<h1 i18n>Welcome</h1>
<p i18n>Hello, {state.name}</p>
<button i18n>Submit</button>

In French that renders:

<h1>Bienvenue</h1>
<p>Bonjour, Ada</p>
<button>Soumettre</button>

Exclude a subtree with no-translate:

<div no-translate>Lips</div>

Regional variants

A dictionary entry can be an object keyed by region, with * as the fallback:

lips.i18n.setDictionary('en', {
  'color': {
    '*':  'color',    // default
    'GB': 'colour',
    'CA': 'colour'
  }
})

lips.setLanguage('en-GB')   // → "colour"

Formats

For anything with variables, define a format entry and reference it with @format.

Variable

lips.i18n.setDictionary('en', {
  'welcome_user': {
    type: 'variable',
    value: 'Welcome back, {name}!'
  }
})
<p @format="welcome_user, { name: state.user.name }"/>

Plural

Keyed by count, with * as the fallback:

lips.i18n.setDictionary('en', {
  'items_count': {
    type: 'plural',
    value: {
      '*': '{count} items',
      '1': '{count} item',
      '0': 'No items'
    }
  }
})
<p @format="items_count, { count: state.items.length }"/>

The parameter must be named count, and a * fallback is required.

Condition

Keyed by an expression over the parameters; the first matching branch wins:

lips.i18n.setDictionary('en', {
  'age_check': {
    type: 'condition',
    value: {
      'age >= 18': 'Welcome in.',
      'age < 18':  '{age}-year-olds are not allowed'
    }
  }
})
<p @format="age_check, { age: input.user.age }"/>

Reacting to language changes

useTranslator runs a callback when the language changes — useful for reloading data from a localized API. It returns an unsubscribe function.

const stop = lips.useTranslator([ 'fr', 'es' ], lang => {
  reloadArticles( lang )
})

// '*' to react to every language
lips.useTranslator('*', lang => document.documentElement.lang = lang )

Translating from code

The i18n API lives on the Lips instance, so reach it through the instance you created — a component’s this does not carry it.

// app.js
export const lips = new Lips()
import { lips } from './app.js'

export const handler = {
  label( text ){
    return lips.i18n.translate( text ).text
  },
  message( key, params ){
    return lips.i18n.format( key, params )
  }
}

translate() returns { text, lang }; format() returns the formatted string.

Prefer the i18n attribute and @format over calling the API from handlers. Only the template forms subscribe to the language signal, so only they re-render automatically when the language changes.