Add i18n to Svelte/Sveltekit projects using Lingui, with gettext-style message as catalog id.
$t`Hello world`
I created this package since I couldn't find any svelte i18n library that allows me to add i18n with a gettext-style approach, which works by:
Most of the i18n libraries for Svelte works by manually defining the message catalog, adding a short descriptive key name manually, and then use the keys in the code. I think this comes with a few drawbacks:
While looking for i18n libraries that support this requirement, I found Lingui which seems to fit this need, and it supports usage in both React and plain JS. While the plain JS version seem to work, I found some drawback when implementing them to my Svelte projects:
babel-plugin-macros
to work, which doesn't seem to work well with Vite.babel-plugin-macros
work we'll need to add an extra tool to do extra transpilation when compiling.Due to the above challenges, I decided to build this library to replicate Lingui's macro functionality on Svelte projects. The basic syntax looks similar to Lingui's macro version, but there are some changes added on top to make it reactive on Svelte. This library also comes with an extractor for both Svelte files and js/ts files to support its customized syntax.
Messages that should be translated to different languages need to be marked for extraction. There are two ways to do this:
Firstly, install this package along with the Lingui packages.
npm install --save @lingui/core svelte-i18n-lingui
npm install --save-dev @lingui/cli
After the packages are installed, create a lingui.config.ts
(or js depending on your project config) and add a basic configuration. An example of a simple project with English base language and Japanese localization:
import { jstsExtractor, svelteExtractor } from 'svelte-i18n-lingui/extractor';
export default {
locales: ['en', 'ja'],
sourceLocale: 'en',
catalogs: [
{
path: 'src/locales/{locale}',
include: ['src/lib', 'src/routes']
}
],
extractors: [jstsExtractor, svelteExtractor]
};
Here we are using svelte-i18n-lingui
's extractors to allow for extracting messages with customized tags from both svelte components and plain js/ts files.
To extract messages as catalogs and compile them for production, @lingui/cli
provides cli commands that we can integrate to our project's workflow. Inside package.json
, add these two commands:
{
"scripts": {
// Add these commands
"extract": "lingui extract",
"compile": "lingui compile --typescript"
}
}
These can be added to pre-commit hooks to make sure that new text are properly extracted and compiled.
Importing the store for the first time will initiate Lingui's i18n instance with a default language and an empty message catalog.
Locale can be changed by accessing the set
method of the locale
store, passing in the desired locale and message catalog. To make sure that only the necessary message catalog is downloaded, use dynamic import, e.g.:
<script lang="ts">
import { locale } from 'svelte-i18n-lingui';
async function setLocale(lang) {
const { messages } = await import(`../locales/${lang}.ts`);
locale.set(lang, messages);
}
</script>
<button on:click={() => setLocale('en')}>Switch to English</button>
<button on:click={() => setLocale('ja')}>Switch to Japanese</button>
To start translating in Svelte files, import the t
store and auto-subscribe to it by prefixing with $
:
<script lang="ts">
import { t } from 'svelte-i18n-lingui';
</script>
<!-- Use directly as text element -->
{$t`hello`}
<!-- Use as attribute or prop -->
<ComponentA propName={$t`hello`} />
<!-- Supports parameterized text, for cases where different language has different order -->
{$t`Proceed to ${$t`cart`}`}
Since the extractor does a static parse of the code, messages must be in plain string to be extractable. Template literals or variables names won't work.
<script lang="ts">
import { t } from 'svelte-i18n-lingui';
const text = 'message';
</script>
{$t`${text}`}<!-- !!! Will not work, extractor cannot find the message to extract !!! -->
Instead, mark the string as extractable first with the provided msg
function, and pass it to the store later on as a plain string (not as tagged template literal)
<script lang="ts">
import { t, msg } from 'svelte-i18n-lingui';
const text = msg`message`;
</script>
{$t(text)}
To include components or elements in the middle of the message, use the provided <T>
component and use #
characters to add slots in the middle of the text to insert elements or components. The first parameter is supplied through the default children snippet (as the content of the component), while subsequent parameters use snippets (second
, third
, fourth
, fifth
corresponding to the index of the parameter, starting from 1 for the default children snippet).
<script lang="ts">
import { T } from 'svelte-i18n-lingui';
</script>
<T msg="Click # for #">
<a href="/about">{$t`here`}</a>
{#snippet second()}{$t`more information`}{/snippet}
</T>
Sometimes we'll need to add a context info for messages that are exactly the same in the base language, but has different meanings in different places (e.g. in English right
can either refer to direction or correctness). We can add a context by passing a message descriptor instead of plain string or literal string:
{$t({
message: 'right',
context: 'direction'
})}
The context will be added on top of the message in the catalog, both of them combined are treated as a unique key for the message. Therefore, the same message with a different context will be extracted as separate entries in the catalog and can have its own translations.
We can also add a comment for the translators reading the message catalog e.g. to let them know how the message is going to be used. This is added as a pure comment on the message catalog and won't affect how the message is extracted or compiled.
{$t({
message: 'text',
context: 'message for translator'
})}
To provide different messages for text with numbers, use the provided $plural
store:
{$plural(count, {
one: '# item',
other: '# items'
})}
Refer to Lingui's pluralization guide for more information on what keys are accepted on different locales.
Since Svelte's stores are meant to be used in Svelte components, using them inside plain js/ts files is a bit awkward since you need to use the get
helper to subscribe, get the value, and then immediately unsubscribe afterwards. svelte-i18n-lingui
provides another way to access Lingui's i18n object through plain strings:
$t
=> gt
$plural
=> gPlural
<T>
component to use the new <LegacyT>
component instead. Otherwise the syntax stays exactly the same with slots.<T>
components to use the <LegacyT>
component instead. Other than the component name and import changes, the syntax stays the same.<T>
component and switch to using snippets instead of slots if you have more than one parameter in the sentence. If there is only one parameter, then there is no need to change anything since default slot and children snippet has the same syntax.origins
option, the line numbers are always empty for <T>
components usage. The line numbers work on any other syntax.Found a bug? Feel free to open an issue or PR to fix it!