Interkit docs

Specify available languages

  • The <AppBase> blockly component has a field languages.
  • It takes a comma-separated list of language codes.
  • The first code is the default language.

Example: de,en,fr

blockly view

The language switcher component

  • <LangSwitch> in blockly
  • can be placed anywhere
  • fields
    • label — a descriptive label, optionally blank; can be translated
    • reloadAfterSwitch — some complex components are not fully reactive w.r.t. localization. Set to yes to force a blunt document.location.reload after the user switches their language.

blockly view

frontend view

Internationalization

“i18n”. Make the app translatable.

i18n: static blockly component text (strings)

Any text used in a Svelte component that is not provided by a field (see below).

import { t, translations, lang } from '../i18n.js'
// translations and lang are stores

const foo1 = t('$foo')
// or in longform, for more control, but no auto-fallback:
const foo2 = get(translations)?.[get(lang)]?.['$foo']

<span>{foo1}: {t('$bar')}</span>
  • t(id) is the shortcut function. It will give you the translation for an id in the active language (lang).
  • It depends on a network connection. If it fails, or page load is slow, you might see instead of the proper value.
  • You can specify the fallback string: t('$foo', 'loading…')
  • For access to other languages’ values, use the translations store.
  • See Implementation overview for other values that i18n.js provides.

i18n: simple blockly component fields (strings)

E.g. a <Span>’s text.

Start the string with a $ sigil. This string will be reactively replaced with its translation.
If no translation is provided, it will default to the “identifier” (the string with the leading $ stripped).

blockly view

i18n: dynamic blockly components

Something using Database sheets, e.g. a <ContentElement_List>

In its fooColumn field, use $lang as a “template variable suffix”. lang will be replace with the current language code. This makes the column name reactively dynamic. E.g.

titleColumn = Orte/title$lang →(turns reactively into)→ Orte/title$de

blockly view admin view

i18n: incompatible components

As of now, some components are not properly reactive:

  • a <Subsection>’s title is propagated via svelte context. The <SubsectionsNav> component builds a breadcrumb-y history array of copies of subsection titles. These can not reactively-retroactively change language.
  • others tbd.

We can use the language switchers reloadAfterSwitch functionality to work around this.

Localization

“l10n”. Translate the contents.

The translation sheet

  • A Database sheet with key translation (name is not enough) which holds translations.
  • needs a column with key _id (again, name is not enough) – corresponds to the sigiled $foo strings, here we use it without the $.
  • multiple columns with keys that match the language codes to hold the actual translations

translation sheet

Heads up: make sure to set the correct keys before you enter any data. If you change the keys later, the data will be lost (the entries are connected to the keys and the connection is not updated on key change)

sheet keys renaming

Default strings

E.g. warnings and hints related to network connectivity.

Please see interkit/i18n_messages.js for predefined strings. Shortened excerpt:

const messages = {
  de: {
    '$init_loading': 'lade....',
    '$appbase_noconnection_network': 'Keine Verbindung (Offline)'
  },
  en: {
    '$init_loading': 'loading....',
    '$appbase_noconnection_network': 'No connection (offline)'
  }
}

The language switcher’s language labels

Translate via the translation sheet, using the internal ID _LangSwitchLanguageOptionLabel.
(See image above).

Chat

The onMessage and onArrive handlers…

  • receive another argument, idiomatically named t – a helper function that takes several strings and returns the one matching the current language. The strings can be provided in three ways:
    1. pipe-separated string
    2. array
    3. object/hash
  • their api argument carries a userLang (code string) and userLangIndex (zero-index) member

Caveat:

  • delayed messages might not reflect current language changes

Examples from the cheatsheet:

// access current language
api.sendText('your language: ' + api.userLang)
api.sendText('your language, index: ' + api.userLangIndex)

// use current language
if (api.userLang === 'en') ...
if (api.userLangIndex === 1) ...
let text1 = ['Deutsch', 'Englisch'][api.userLangIndex]
let text2 = {de: 'Deutsch', en: 'Englisch'}[api.userLang]

// use text localized to current user language
export const onMessage = async (msg, api, t) => {
  api.sendChoice({ a: t('Ja|Yes'), b: t('Nein|No') })
}

// the t helper function takes pipe-separated strings, arrays or objets:
api.sendText(t('Ja|Yes'))
api.sendText(t(['Ja', 'Yes']))
api.sendText(t({ de: 'Ja', en: 'Yes' })) // order-independant

// use sendTextT & sendChoiceT shortcuts, equivalently
api.sendTextT('Tschüß|Bye')
api.sendChoiceT({ a: 'Ja|Yes', b: ['Nein', 'No'] })

Implementation overview

  • the current language is stored in a user’s userProjectData
    • userLang has the language code
    • userLangIndex has a zero-based index matching the order in <AppBase>.languages
  • both are exported by interkit/i18n.js as stores. Use $lang / $langIndex
  • also notably exported:
    • $translations — an object store constructed from the sheet with all translations
    • $langs — array from <AppBase>.languages
    • setUserLang — update user’s language
  • reactive blockly/component fields
    • are injected via interkit-blockly/blockly/initCodeGenerator.js
    • the referenced stores are imported by way of svelte-admin/src/BlocklyEditor.svelte

TODOs / possible improvements

  • guess language from browser/navigator language?
  • set lang HTML attributes?
  • contexts for translations strings/IDs
  • other gettext-like functionalities (pluralization, dates)