LocalizationNew

Localization is the process of adapting a product’s translation to a specific country or region. Our team is committed to providing a high-quality experience for all our international user.

Nord provides a lightweight solution for localizing its components. Not all components need localizing, as for the most part snippets of text are set per instance. For example, the label on an Input will likely be changed every time you use it. Those cases are an app-level concern.

However, some components have text which has no reason to change per instance. For example, the usage instructions in the footer of the Command menu, or the accessible labels for next/previous month buttons of the Calendar.

For the latter cases, we provide a localization mechanism. It is not possible or feasible for Nord to anticipate every locale in which the components might be consumed, so we rely on individual teams and applications to translate and configure localization. To ensure Nord components work out of the box, we bundle translations for en-US.

How does it work? #

We observe the lang attribute on the <html> element of a page, and use this to decide which translations to use for components. If a matching translation is found, the components will use this. Otherwise, the components will fall back to using the built-in en-US translations.

For this reason, it is imperative that you correctly set the lang attribute on the <html> element. Not doing so is considered an accessibility failure, and will also cause unexpected or incorrect localization of components.

We use a MutationObserver to observe and respond to change to the lang attribute on <html>. So whilst you can do a full page reload whenever the language is changed, we also support changing languages on the fly without a full reload.

Registering translations #

To add a translation, we provide a registerTranslation function, which accepts an object containing localized text for a specific language/region.

import { registerTranslation } from "@nordhealth/components"

registerTranslation({
  $lang: "fi",
  $name: "Suomi",
  $dir: "ltr",

  // translations go here
})

For a full example of a real world translation, see the example translation section.

If you use TypeScript, we export a Translation type which will guide you in creating translations, and will raise compile time errors if you are missing any pieces of text:

import { registerTranslation, Translation } from "@nordhealth/components"

const yourTranslation: Translation = {
  // translation goes here
}

registerTranslation(yourTranslation)

For convenience you can also register multiple translations at once:

import { registerTranslation, Translation } from "@nordhealth/components"

const fi: Translation = { /* translation */}
const no: Translation = { /* translation */}

registerTranslation(fi, no)

You may have to support many languages, and not want to pay the cost of registering all translations on page load. In this case, you can register translations as needed, by dynamically importing your translations modules:

import { registerTranslation } from "@nordhealth/components"

function loadTranslation(lang) {
  import(`path/to/your/translations/${lang}.js`).then(({ default: translation }) => registerTranslation(translation))
}

someDropdown.addEventListener("change", e => {
  const lang = e.target.value

  loadTranslation(lang).then(() => {
    document.documentElement.lang = lang
  })
})

Example translation #

Each translation has the following top-level properties:

Other top-level properties correspond to the component name in which the text is used, and below that the identifiers for individually translated snippets.

Note that there are a mix of strings, arrays, and functions as values. This is to give maximum flexibility when translating, and to allow for properly handling language-specific grammar.

Below is an example translation for the Finnish locale, which can be used as a reference:

export default {
  $lang: "fi",
  $name: "Suomi",
  $dir: "ltr",

  "nord-command-menu": {
    instructions: "Paina 'Enter' vahvistaaksesi valinnan tai 'Escape' peruuttaaksesi",
    inputLabel: "Kirjoita komento jonka haluat suorittaa.",
    footerArrowKeys: "Siirry",
    footerEnterKey: "Valitse",
    footerEscapeKey: "Esc sulje",
    footerBackspaceKey: "Siirry takaisin",
    noResults: searchTerm => `Ei tuloksia haulle "${searchTerm}"`,
    tip: "Vinkki: jotkin haut vaativat tarkan hakutermin. Koita kirjoittaa koko hakutermi kokonaisuudessaan, tai kokeile toista sanaa tai fraasia.",
    placeholder: "Kirjoita komento tai hakusana…",
  },

  "nord-calendar": {
    prevMonthLabel: "Edellinen kuukausi",
    nextMonthLabel: "Seuraava kuukausi",
    monthSelectLabel: "Kuukausi",
    yearSelectLabel: "Vuosi",
  },

  "nord-date-picker": {
    modalHeading: "Valitse päivämäärä",
    closeLabel: "Sulje ikkuna",
    buttonLabel: "Valitse päivämäärä",
    selectedDateMessage: "Valittu päivämäärä on",
  },

  "nord-modal": {
    closeLabel: "Sulje ikkuna",
  },

  "nord-nav-toggle": {
    label: "Näytä/Piilota valikko",
  },

  "nord-textarea": {
    remainingCharacters: (remaining: number) => `${remaining} merkkiä jäljellä`,
  },

  "nord-notification": {
    dismissLabel: "Sulje ilmoitus",
  },

  "nord-message": {
    unreadLabel: "Lukematon",
  },
}

Localization framework integration #

You can integrate the Nord translations easily with a localization framework by writing a wrapper around it.

The following TypeScript example integrates with i18next:

import type { Translation } from '@nordhealth/components'
import i18next from 'i18next'
import { useSupportedLocales } from './useSupportedLocales.js'

export const useNordTranslation = (locale: string) => {
  const supportedLocales = useSupportedLocales()

  // we want to fix the t function, so that it always matches the given locale,
  // allowing us to control when the translations are switched over
  const t = i18next.getFixedT(locale)

  const translation: Translation = {
    $lang: locale,
    $name: supportedLocales[locale].name,
    $dir: supportedLocales[locale].dir,

    'nord-command-menu': {
      placeholder: t('common.nordComponents.commandMenu.placeholder'),
      instructions: t('common.nordComponents.commandMenu.instructions'),
      inputLabel: t('common.nordComponents.commandMenu.inputLabel'),
      footerArrowKeys: t('common.nordComponents.commandMenu.footerArrowKeys'),
      footerEnterKey: t('common.nordComponents.commandMenu.footerEnterKey'),
      footerEscapeKey: t('common.nordComponents.commandMenu.footerEscapeKey'),
      footerBackspaceKey: t(
        'common.nordComponents.commandMenu.footerBackspaceKey'
      ),
      noResults: (searchTerm: string) =>
        t('common.nordComponents.commandMenu.noResults', {
          searchTerm,
        }),
      tip: t('common.nordComponents.commandMenu.tip'),
    },
  }

  return translation
}

Support #

Need help with localization? Please head over to the Support page for more guidelines and ways to contact us.


Was this page helpful?

YesNo
Send feedback

We use this feedback to improve our documentation.

 
Edit page