Internationalization (i18n) and localization (l10n)
Table of contents
Specify available languages
- The
<AppBaseAdvanced>
blockly component has a fieldlanguages
. - It takes a comma-separated list of language codes.
The first code is the default language.
Example:de,en,fr
- The language codes should follow the standard for language features to work (like the browser’s built-in hyphenation, or translation extensions)
- Language is stored per user, so you will also need e.g. an
<AnonymousLogin>
. There you can override the default language.
The language switcher component
<LangSwitch>
in blockly- can be placed anywhere
- fields
label
— a descriptive label, optionally blank; can be translatedreloadAfterSwitch
— some complex components are not fully reactive w.r.t. localization. Set toyes
to force a bluntdocument.location.reload
after the user switches their language.
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 anid
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. - Since lang is a store which is initialized from user data,
it might be not available in the non-reactive JS part. E.g., in a component,
export let someParam = t('$someparam')
will likely not work,
but providing adefaultValue
for the param in its yaml will.
You might be able to work around this with$: …
.
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).
i18n: dynamic blockly components
Something using Database sheets, e.g. a <DataLoaderMulti>
with a <DataList>
of <DataCell>
s.
- Each column you want to translate, has to be multiplied into and suffixed with all languages, e.g.
name
→name$de
+name$en
- The sheet also needs an extra, meta column with suffix
$lang
, e.g.
name$lang
The contents of this column might be used as a fallback. $lang
acts as a “template variable suffix”.lang
will be replace with the current language code. This makes the column name reactively dynamic, e.g.
column =Cats/name$lang
→(turns reactively into)→Orte/name$de
- In Blockly, you select the
…$lang
column.
i18n: incompatible components
Some components might not be properly reactive, e.g.:
- the legacy
<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.
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
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)
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:- pipe-separated string
- array
- object/hash
- their
api
argument carries auserLang
(code string) anduserLangIndex
(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 codeuserLangIndex
has a zero-based index matching the order in<AppBaseAdvanced>.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<AppBaseAdvanced>.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
- are injected via
TODOs / possible improvements
- guess language from browser/navigator language?
- set
lang
HTML attributes? - contexts for translations strings/IDs
- other gettext-like functionalities (pluralization, dates)