Documentation
Installation
npm install @terrygonguet/auto-i18n
Usage
Until the Svelte ecosystem agrees on a way to add plugins to SvelteKit, you'll have to integrate the different parts of auto-i18n
into your app yourself. Don't worry it's pretty easy 😉.
auto-i18n
makes extensive use of Svelte 5's reactivity primitives. All you have to do is use simple functions and we handle the auto loading and caching and acrobatics needed to have SSR and our cool editor. The function calls will be re-run whenever needed to keep your UI in sync.
Like pants, auto-i18n
comes in two parts: client and server. The server part adds routes to your app so the client can load translations and send new ones. The client part serves as an internationalisation library and loads the editor for changing the translations.
Server
On the server most of the business happens in the hooks.server.ts
file. You can import the createAutoI18NHandle
function from @terrygonguet/auto-i18n/server
and use it to create the titular handle function. This function is a fully self contained handle hook and can be added to your app via the sequence function.
hooks.server.ts
import { sequence } from "@sveltejs/kit/hooks"
import { createAutoI18NHandle } from "@terrygonguet/auto-i18n/server"
const i18nHandle = createAutoI18NHandle({
fetchCategory,
canFetchCategory,
fetchAll,
canFetchAll,
update,
canUpdate,
})
export const handle = sequence(i18nHandle, ({ event, resolve }) => {
// do your hook stuff
return resolve(event)
})
You probably want to run this function after your session/authentication but before the rest of SvelteKit.
All the values passed to the createAutoI18NHandle
function are functions too. Those whose name start with "can" are guards, allowing you to grant or deny access to any part of the generated routes. The other functions are here to transfer data from your storage solution to the format that auto-i18n
expects. Please refer to the API section for details.
Client
The client side is more involved. Fundamentally, all you have to do is create an instance of the AutoI18N
class and pass it around. Your components can then use the translate()
(aliased to t()
) method to display the translation strings.
As usual, everything gets more complex when we take SSR into account. The recommended flow is to have a layout.server.ts
file to resolve which language to display to the user; a layout.ts
file to create the AutoI18N
instance that will be available throughout your application and now you can use page.data
or $props().data
(for pages) to get your translation strings anywhere.
layout.server.ts
export const load = async ({ cookies, request }) => {
// do whatever you want to get the user's language settings
return { lang, supportedLangs, fallbackLang }
}
layout.ts
export const load = async ({ fetch, data: { lang, supportedLangs, fallbackLang } }) => {
const i18n = new AutoI18N({ lang, supportedLangs, fallbackLang, fetch })
return { i18n, t: i18n.t.bind(i18n), c: i18n.c.bind(i18n) }
}
+page.svelte
<script lang="ts">
let { data } = $props()
let { t } = $derived(data)
</script>
<p>{@html t("category", "key")}</p>
Component.svelte
<script lang="ts">
import { page } from "$app/state"
let { t } = $derived(page.data)
</script>
<p>{@html t("category", "key")}</p>
From there, you can write whatever code you want to trigger i18n.showEditor()
to show the editor UI (simple button, shortcut keybinds etc).
Interpolation
Static strings are not enough to fully localize an application, some parts of the text must be dynamic. To that end, auto-i18n
has a few interpolation utilities.
Simple value insertion {{name}}
The simplest interpolation: inserts the corresponding value in the string.
If your string contains "The value is: {{some_name}}"
, calling t(category, key, { values: { some_name: "something" } })
will produce "The value is: something"
.
Nested translation {{$t category key [lang]}}
Used when you need to insert the value of another translation string in your result.
Let's say you have the following strings in the category "ui":
- key: "lang"
- language "en":
"English"
- language "fr":
"Français"
- language "en":
- key: "other_lang":
- language "en":
"The other language is called {{$t ui lang fr}}"
- language "en":
Now calling t("ui", "other_lang")
will return "The other language is called Français"
. If we left out the language tag then the current language will be used, English in this case, and the returned string would be "The other language is called English"
.
The options are passed down to the nested call to translate()
.
Conditional insertion {{$if name true_value [$else false_value]}}
Sometimes you need to show or hide some text conditionally. The $else
value is optional, an empty string will be inserted if it is left out.
If your string contains "The answer is {{$if condition correct $else wrong}}"
, calling t(category, key, { values: { condition: true } })
will produce "The answer is correct"
; and "The answer is wrong"
when we give it { condition: false }
.
Pattern matching {{$match name [...patterns]}}
Pattern matching is useful for plural or gender rules. It will match the given value with one of the patterns or the default pattern if no other match.
Each pattern is composed of a value to be matched, a colon :
and the value to insert. An underscore _
denotes the default value to use if no other pattern matches. It is not required but strongly encouraged. All values are converted to strings before matching.
If your string contains "I have {{$match n 0:no 1:one _:lots of}} sheep"
, calling t(category, key, { values: { n } })
will produce:
n = 0
->"I have no sheep"
n = 1
->"I have one sheep"
n = 2
->"I have lots of sheep"
The editor
The editor is auto-i18n
's super power. We don't have an opinion on how to trigger showing the editor, all you have to do is call showEditor()
on your AutoI18N
instance; and then call hideEditor()
when you're done.
Once the editor is open, all you have to do is click on the string you want to edit and a dialog will open, allowing you to make any changes you want. If you cannot click on the text itself, there is a "see all" button in a corner of the screen that will list all the keys used on the page.
This is possible because of Svelte's {@html ...} tag. In standard use we insert your translation strings as-is (because regular text is valid HTML). When we open the editor we wrap those strings in a <div>
with display: contents
applied to it. In most cases this div should be invisible and have no impact on the layout of your page but now we can detect click on it and use that to show you a popup with full context to edit the translation.
API
class AutoI18N
from @terrygonguet/auto-i18n
constructor(options: AutoI18NConstructorOptions)
options.lang: string
- the language in which to diplay your app to the useroptions.supportedlangs: string[]
- a list of all the languages supported by your appoptions.fallbackLang: string
- the language to use if a translation isn't available in the current language or if we tried to use an unsupported languageoptions.preload: string[]
- a list of categories to start loading immediately. Does not block the constructor (ie: the tranlations will not be synchronously available)options.fetch: typeof fetch
- a fetch function for your environment. Typically supplied by SvelteKit in thelayout.ts
orlayout.server.ts
load
functions
lang: string
Read only identifier for the current language.
supportedLangs: string[]
Read only identifiers for the supported languages.
fallbackLang: string[]
Read only identifier for the fallback language.
loadedCategories: Iterator<string>
Read only iterator listing the loaded categories.
isEditorShown: boolean
Read only boolean indicating whether the editor is displayed or not.
load(category: string, options): Promise<void>
Loads the specified category. Returns a promise that is fulfilled when the category has been loaded, may be immediately.
options.lang: string
- the language in which to load the category. Defaults to the current languageoptions.skipIfCached: boolean
- skip loading data from the server if it has been loaded before
loadAll(options): Promise<void>
Loads multiple categories (all if not specified) in multiple languages (all if not specified). Returns a promise that is fulfilled when the category has been loaded.
options.categories: string[]
- the list of categories to load. All if not specifiedoptions.langs: string[]
- the list of languages in which to load the categories. All supported if not specified
setlang(lang: string): Promise<void>
Change the display language, loading all the currently loaded categories in that new language. Returns a promise that is fulfilled when the new data is loaded.
translate(category: string, key: string, options: TOptions): string
Aliased as t()
.
Gets and interpolate a translation string.
category: string
- the category to usekey: string
- the key to useoptions.values: Record<string, TValue>
- the values to use when interpolatingoptions.autoload: boolean
- whether to load the category if missing. Defaults totrue
options.lang: string
- the language to use to render and load the string. Defaults to the current languageoptions.overrideMissing: string
- the string to use if the data is missingoptions.editor
- the editor config to use for this string. Usetrue
for defaults,false
to disable and an object for specific config
content(content: string, options: Object): string
Aliased as c()
.
Inserts some external HTML without any sanitizing. This function does nothing to the content
but makes auto-i18n
aware of it, so we can show you something when the editor is open.
content: string
- the raw HTML content to insertoptions.url: string
- an optional url to where the content can be editedoptions.editor
- the editor config to use for this content. Usetrue
for defaults,false
to disable and an object for specific config
raw(category: string, key: string, options): string | undefined
Returns the raw, saved value for this category/key and undefined
in unavailable.
category: string
- the category to usekey: string
- the key to useoptions.autoload
- whether to load the category if missing. Defaults tofalse
options.lang
- the language to use to render and load the string. Defaults to the current language
rawCategory(category: string, options): Record<string, string>
Returns the loaded data for a category.
category: string
- the category to get data foroptions.autoload: boolean
- whether to load the category if missing. Defaults tofalse
options.lang: string
- the language to use. Defaults to the current languageoptions.includeFallback
- whether to merge the data from the fallback language into the returned value (if available). Defaults tofalse
interpolate(text: string, values: Object, options: Object): string
Interpolates patterns in text
with the supplied values
. See the section on Interpolation for details.
text: string
- the string to interpolatevalues: Object
- the values to use when interpolatingoptions: Object
- the options to pass to thetranslate()
function when using the$t
interpolation
showEditor(options: Object)
Loads and mounts the editor component. See the section on the editor for details.
options.autoload: boolean
- set totrue
to automatically load all categories for all languages. Defaults tofalse
hideEditor()
Hides and unmounts the editor component.
createAutoI18NHandle
from @terrygonguet/auto-i18n/server
Helper function to create a single handler that behaves like registering multiple API endpoints. The function takes an object of hooks that will be called to interact with your system.
fetchCategory(args): { [key: string]: string }
Hook that will be called when a user wants to load a specific category in a language. Can be async.
args.where: { lang: string, category: string }
args.event: RequestEvent
- SvelteKit's RequestEvent
fetchAll(args): { [key: string]: string }
Hook that will be called when a user wants to load a multiple categories and/or in multiple languages. Can be async.
args.where: { lang: string[], category: string[] }
- eitherlang
orcategory
can be omited to signify "all"args.event: RequestEvent
- SvelteKit's RequestEvent
update(args, options): void
Hook that will be called when a user wants to save translation strings. Can be async.
args.data: { category: string; key: string; langs: { [lang: string]: string } }
- the payload to saveoptions.event: RequestEvent
- SvelteKit's RequestEvent
canFetchCategory(args): boolean
canFetchAll(args): boolean
canUpdate(args, options): boolean
Hooks that will be called before the corresponding hooks with the same args. Needs to return whether or not to allow the request. Can be async.
Defaults to allowing all requests if omited.