Internationalization (i18n)

June 28, 2019 · View on GitHub

Stelace Instant template includes English (default) and French translations out of the box, and can be translated to many more languages.

Structure

During build all your translations are transformed and merged from:

  • local entries defined in .yaml translation files committed to your git repository
  • your entries saved with Stelace Content API enabling live content updates.

Merging happens on build time, providing best performance for end-user.

Vue app still offers up-to-date contents since it requests Content API during boot, in a non-blocking way.

Note: You can change your default locale in .env files. All translations for this locale will be included in app bundle.

Local content entries

Translations .yaml files are located in src/i18n/source folder.

For easier maintenance translations are nested under entry names and a single translation key maps to all languages:

entry:
  field:
     en: English
     fr: French
  nested:
    field:
      en: Plain {SERVICE_NAME} text
      en_gb: You can translate to locales rather than languages too
      fr: Texte en clair {SERVICE_NAME}
      es: Texto sin formato
      context: Additional context can help translators
      # You may also come across translations keys suffixed with '__EDITOR_LABEL'.
      # They allow to provide content managers with additional context in several languages
      # when using Stelace Dashboard.

A .json file is generated for any language having at least one translation.

{
  // fr.json
  "entry": { // equivalent to Stelace Content API Entry object
    "field": "French",
    "nested.field": "Texte en clair {SERVICE_NAME}"
  },
  // …

src/i18n/theme yaml files can be used to keep your own translations in their own files and ease updates.

src/i18n/email translations are used in emails sent by your Stelace Email API calls or Workflows.

Content API entries

Stelace Content API enables your content team / translators to edit contents live.

Our headless CMS implementation offers both fresh content and best performance for your visitors, unlike other solutions available.

We merge Content Entry API contents with local translations during each build and fetch new contents in a non-blocking way on each pageload :rocket:.

After changing default translations in codebase, you can upload these running:

yarn translate
# No need to run `translate:prod` since we don’t merge translations from Content API,
# but just use local defaults.

and then:

yarn deploy:translations
# or
yarn deploy:prod:translations
# STELACE_SECRET_API_KEY will be loaded from `.env.development` or `.env.production`
# depending on which command you use.
# Your are free to set any live or test secret key as env variable during your deployment process.

This will allow any authorized member of your team to browse all default contents used on your website from Stelace dashboard.

Values edited in dashboard by your team members will be saved to entry fields while default values are set in metadata._instant namespace.

This way default contents uploaded from local yaml files (compiled to json) can be kept in sync with Content API after each deployment.

On the other hand your content team can edit content live without having to tamper with default translations in your git repository.

Building JSON files

After changes on the .yaml files, you have to run yarn translate to trigger a refresh of Vue app and render new contents, based on new json translation files automatically built.

Note: yarn translate:prod will use your .env.production publishable API key to retrieve Content API entries.

Adding translations

Before adding new translations, you may want to ensure no existing one is appropriate with a search in src/i18n/source and src/i18n/theme folders in your editor.

A slight change in either your new content or in existing translations could spare one extra entry to translate.

Markdown support

Enjoy rich-text content in your translations with markdown syntax.

All commonMark syntax is supported, along with Github flavored markdown tables and strikethrough.

You just have to add [markdown] suffix to any translation key to enable automatic rendering to HTML during build.

not_markdown:
  en: Plain {SERVICE_NAME} text
  fr: Texte {SERVICE_NAME}
terms_optin[markdown]:
  en: I read and agree to the [Terms of Service]({terms_path}) applicable on {SERVICE_NAME}.
  fr: J’ai lu et accepte les [Conditions Générales d’Utilisation]({terms_path}) de {SERVICE_NAME}.

will be rendered as the following in src/i18n/build/en.json:

{
  "not_markdown": "Plain {SERVICE_NAME} text",
  "terms_optin": {
    "editable": "I read and agree to the [Terms of Service]({terms_path}) applicable on {SERVICE_NAME}.",
    "transform": "markdown",
    "transformed": "<p>I read and agree to the <a href=\"%7Bterms_path%7D\">Terms of Service</a> applicable on {SERVICE_NAME}.</p>\n"
  }
}

As a convention, having content object value with editable and transformed keys mean this content needs to be rendered as HTML in Vue app.

This way you can also edit markdown contents using Content API outside this project as long as you keep transformed value in sync with editable. Note that it is automatically done when editing from Stelace Dashboard.

Multi-line with YAML

You can use >, |, >-, |- YAML block styles to write multi-line strings which is often needed in Markdown or for long text.

You generally want to use >- to ignore new lines and prevent parser from adding a line break at the end.

Here are some very useful examples on StackOverflow.

ICU Syntax

You may have noticed in markdown example above that we use ICU MessageFormat syntax ({SERVICE_NAME}).

You can learn and experiment on this nice website.

ICU enables advanced i18n with plurals, gender and conditionals using standard syntax.

We are using and contributing to vue-intl rather than vue-i18n for built-in ICU Message Format support.

Multi-language

For multiple languages support, you can consider deploying several websites with their own domain names if you can afford additional maintenance, using Airbnb’s strategy for best SEO results.

Note that you can still use the same Stelace database and share assets across your localized websites.

You can also go for the single-domain route with several languages on the same website.

If you opt for a multi-language website, best strategy is to lazyload additional languages as they are needed during user navigation. Good news: it’s how we handle this in Stelace Instant.

Multi-currency

Exposing multiple currencies is another story and can confuse you users if not done right.

Only one currency should probably be shown to your users at a time. This template includes a $fx sample wrapper that could apply live exchange rate conversion in the app if assets have prices set in different currencies.

Exchange rates applied by your payment provider may be different though, and additional fees can apply, so you may want to warn your users about potential price changes unless you compensate exchange difference yourself or on the seller’s account.

Custom formats

You can use custom formats for date, time and number if needed, as per FormatJS docs using vue-intl:

const currencyFormat = {
  number: {
    customCurrency: {
      // currency: 'EUR',
      style: 'currency',
      minimumFractionDigits: 0
    }
  }
}

Vue.setLocale(lang)
Vue.registerFormats(lang, currencyFormat)

And then you can format with appropriate currency on the fly:

  {{ $formatNumber(asset.price, { format: 'customCurrency', currency: 'USD' }) }}

Should currency be set to EUR in currencyFormat format, you would still be able to override with USD.

You can also use these custom formats in translation files:

Discounted price is {{ price, number, customCurrency }}

Used libraries