Last week I had the pleasure of being at the Svelte Society London gettogether. The evening started with a talk about i18n by @loris_sigrist from inlang.
I was impressed by the story of paraglide-js which compiles translatable strings into tree-shakable javascript functions. This can improve both the developer experience, with IntelliSense, and the user experience, with reduced bundle sizes.
In this post I cover:
All the code and commits are in https://github.com/jldec/paraglide.
pnpm create svelte@latest paraglide
cd paraglide
pnpm install
git init && git add -A && git commit -m init
pnpm dev
I followed the inlang docs to run paraglide init
.
The CLI detected my uncommited changes and offered me a chance to exit before continuing.
pnpx @inlang/paraglide-js@latest init
╭─────────────────────────────────────╮
│ │
│ Welcome to inlang Paraglide-JS 🪂 │
│ │
╰─────────────────────────────────────╯
ℹ You have uncommitted changes.
Please commit your changes before initializing inlang Paraglide-JS. Committing outstanding
changes ensures that you don't lose any work, and see the changes the paraglide-js init
command introduces.
✔ Do you want to initialize inlang Paraglide-JS without committing your current changes?
Yes
ℹ Creating a new inlang project in the current working directory.
✔ Successfully created a new inlang project.
✔ Added @inlang/paraglide-js to the devDependencies in package.json.
✔ Successfully added the compile command to the build step in package.json.
✔ Are you using VSCode?
Yes
✔ Added the inlang vs code extension to the workspace recommendations.
╭──────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ inlang Paraglide-JS has been set up sucessfully. │
│ │
│ 1. Run your install command (npm i, yarn install, etc) │
│ 2. Run the build script (npm run build, or similar.) │
│ 3. Done :) Happy paragliding 🪂 │
│ │
│ For questions and feedback, visit https://github.com/inlang/monorepo/discussions. │
│ │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────╯
I then ran the suggested install and build.
pnpm install
pnpm build
git add -A
git commit -m 'Add paraglide'
And pushed the commit to a new repo on GitHub.
git remote add origin https://github.com/jldec/paraglide.git
git push
The commit shows that @inlang/paraglide-js@latest init
added paraglide to package.json
, and created project.inlang/settings.json
.
When I opened the project in VS Code, I noticed the recommended inlang VS Code extension.
Once this was installed, I used 'Inlang: extract message' in the command palette, to extract "Home" and "About" from src/routes/Header.svelte
into /messages/en.json
.
<li aria-current={$page.url.pathname === '/' ? 'page' : undefined}>
<a href="/">{m.home()}</a>
</li>
<li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>
<a href="/about">{m.about()}</a>
</li>
/messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"home": "Home",
"about": "About"
}
Once things are configured properly, the extension will show IntelliSense and translated text for each m.<key>()
.
I also had to add an import for the m
namespace, using a $msgs
alias which I created in svelte.config.js
.
import * as m from '$msgs';
kit: {
alias: {
'$msgs': 'src/paraglide/messages.js'
},
}
Fink makes it easy for translators to work with message files in a git repo, without having to clone the repo or use the git CLI directly.
You can specify the repo in the URL or using the input field at https://fink.inlang.com/. Once authenticated and connected to the repo, inlang will rememer your recent projects in the dashboard.
I found the English messages as extracted, and added de
German translations using Fink.
Fink then pushed a commit to the repo in GitHub.
When I pulled down the changes, the new German messages were in /messages/de.json
.
/messages/de.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"home": "Startseite",
"about": "Diese App"
}
To re-render the page Header when the language changes, I hacked Counter.svelte, to call setLanguageTag()
whenever the count toggles beteween odd and even.
[!tip] Normally users would change the language URL themselves, or it can be detected from the browser.
import { setLanguageTag } from '../paraglide/runtime';
let count = 0;
$: setLanguageTag(count % 2 ? 'en' : 'de');
I also added a reactive Svelte store called $lang
in src/lib/lang.ts
.
import { readable } from 'svelte/store';
import { onSetLanguageTag, languageTag } from '../paraglide/runtime';
export const lang = readable<string>('en', (set) => {
onSetLanguageTag(set);
});
The value of the store is used as the key to the re-render the Header in +layout.svelte
.
<script>
import Header from './Header.svelte';
import './styles.css';
import { lang } from '$lib/lang';
</script>
<div class="app">
{#key $lang}
<Header />
{/key}
And the result looks like this:
The repo https://github.com/jldec/paraglide
is public, so anyone can try it.
{ "path": "/blog/getting-started-i18n-with-paraglide-and-fink", "attrs": { "title": "Getting started with SvelteKit and paraglide-js", "author": "Jürgen Leschner", "splash": { "image": "/images/airport.webp" }, "date": "2024-01-08", "layout": "BlogPostLayout", "excerpt": "First steps enabling i18n on a SvelteKit project.\n" }, "md": "# i18n with inlang\n\nLast week I had the [pleasure](the-web-is-for-everyone.md) of being at the Svelte Society London gettogether. The evening started with a talk about i18n by [@loris_sigrist](https://twitter.com/loris_sigrist) from [inlang](https://inlang.com/).\n\nI was impressed by the story of [paraglide-js](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) which compiles translatable strings into tree-shakable javascript functions. This can improve both the developer experience, with IntelliSense, and the user experience, with reduced bundle sizes.\n\nIn this post I cover:\n 1. Trying out paraglide-js with the SvelteKit demo app.\n 2. Extracting strings using the inlang VS Code extension.\n 3. Translating messages in the browser using Fink.\n 4. Making the translated messages appear in the SvelteKit demo app.\n\nAll the code and commits are in [https://github.com/jldec/paraglide](https://github.com/jldec/paraglide).\n\n## Create a new SvelteKit demo app\n\n```sh\npnpm create svelte@latest paraglide\n\ncd paraglide\npnpm install\ngit init && git add -A && git commit -m init\npnpm dev\n```\n\n![SvelteKit Demo app screenshot](/images/sveltekit-demo.webp)\n\n## Add paraglide-js\nI followed the inlang [docs](https://inlang.com/m/gerre34r/library-inlang-paraglideJs#getting-started) to run paraglide `init`.\n\nThe CLI detected my uncommited changes and offered me a chance to exit before continuing.\n```sh\npnpx @inlang/paraglide-js@latest init\n\n ╭─────────────────────────────────────╮\n │ │\n │ Welcome to inlang Paraglide-JS 🪂 │\n │ │\n ╰─────────────────────────────────────╯\n\nℹ You have uncommitted changes.\n\nPlease commit your changes before initializing inlang Paraglide-JS. Committing outstanding \nchanges ensures that you don't lose any work, and see the changes the paraglide-js init \ncommand introduces.\n\n✔ Do you want to initialize inlang Paraglide-JS without committing your current changes?\nYes\n\nℹ Creating a new inlang project in the current working directory.\n✔ Successfully created a new inlang project.\n✔ Added @inlang/paraglide-js to the devDependencies in package.json.\n✔ Successfully added the compile command to the build step in package.json.\n✔ Are you using VSCode?\nYes\n\n✔ Added the inlang vs code extension to the workspace recommendations.\n\n ╭──────────────────────────────────────────────────────────────────────────────────────╮\n │ │\n │ inlang Paraglide-JS has been set up sucessfully. │\n │ │\n │ 1. Run your install command (npm i, yarn install, etc) │\n │ 2. Run the build script (npm run build, or similar.) │\n │ 3. Done :) Happy paragliding 🪂 │\n │ │\n │ For questions and feedback, visit https://github.com/inlang/monorepo/discussions. │\n │ │\n │ │\n ╰──────────────────────────────────────────────────────────────────────────────────────╯\n```\n\nI then ran the suggested install and build.\n```sh\npnpm install\npnpm build\ngit add -A\ngit commit -m 'Add paraglide'\n```\n\nAnd pushed the commit to a new repo on GitHub.\n```sh\ngit remote add origin https://github.com/jldec/paraglide.git\ngit push\n```\n\nThe [commit](https://github.com/jldec/paraglide/commit/548a1a85b13435d93bde99368148dd417f77b50c) shows that `@inlang/paraglide-js@latest init` added paraglide to `package.json`, and created `project.inlang/settings.json`.\n\n## Extract strings with the inlang VS Code extension\nWhen I opened the project in VS Code, I noticed the recommended [inlang VS Code extension](https://inlang.com/m/r7kp499g/app-inlang-ideExtension).\n\nOnce this was installed, I used 'Inlang: extract message' in the command palette, to extract \"Home\" and \"About\" from `src/routes/Header.svelte` into `/messages/en.json`.\n\n```html\n<li aria-current={$page.url.pathname === '/' ? 'page' : undefined}>\n <a href=\"/\">{m.home()}</a>\n</li>\n<li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>\n <a href=\"/about\">{m.about()}</a>\n</li>\n```\n\n**/messages/en.json**\n```json\n{\n \"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n\t\"home\": \"Home\",\n\t\"about\": \"About\"\n}\n```\n\nOnce things are configured properly, the extension will show IntelliSense and translated text for each `m.<key>()`.\n\n![Screenshot of VS Code extension showing translated text](/images/translations-in-editor.webp)\n\nI also had to add an import for the `m` namespace, using a `$msgs` alias which I created in `svelte.config.js`.\n\n```js\nimport * as m from '$msgs';\n```\n\n```js\nkit: {\n alias: {\n '$msgs': 'src/paraglide/messages.js'\n },\n}\n```\n\n## Translation with the Fink editor\nFink makes it easy for translators to work with message files in a git repo, without having to clone the repo or use the git CLI directly.\n\nYou can specify the repo in the URL or using the input field at [https://fink.inlang.com/](https://fink.inlang.com/). Once authenticated and connected to the repo, inlang will rememer your recent projects in the dashboard.\n\nI found the English messages as extracted, and added `de` German translations using Fink.\n\n![Fink screenshot with English messages and German translations](/images/fink-deutsch.webp)\n\nFink then pushed a [commit](https://github.com/jldec/paraglide/commit/98b1b618a4d0badb56bde98bd9a6ec9acd03706a) to the repo in GitHub.\n\nWhen I pulled down the changes, the new German messages were in `/messages/de.json`.\n\n**/messages/de.json**\n```json\n{\n\t\"$schema\": \"https://inlang.com/schema/inlang-message-format\",\n\t\"home\": \"Startseite\",\n\t\"about\": \"Diese App\"\n}\n```\n\n## Show translated messages in the SvelteKit demo app\n\nTo re-render the page Header when the language changes, I hacked Counter.svelte, to call `setLanguageTag()` whenever the count toggles beteween odd and even.\n\n> [!tip]\n> Normally users would change the language URL themselves, or it can be detected from the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language).\n\n```ts\nimport { setLanguageTag } from '../paraglide/runtime';\n\nlet count = 0;\n$: setLanguageTag(count % 2 ? 'en' : 'de');\n```\n\nI also added a reactive Svelte store called `$lang` in `src/lib/lang.ts`.\n\n```ts\nimport { readable } from 'svelte/store';\nimport { onSetLanguageTag, languageTag } from '../paraglide/runtime';\n\nexport const lang = readable<string>('en', (set) => {\n onSetLanguageTag(set);\n});\n```\n\nThe value of the store is used as the key to the re-render the Header in `+layout.svelte`.\n\n```html\n<script>\n\timport Header from './Header.svelte';\n\timport './styles.css';\n\timport { lang } from '$lib/lang';\n</script>\n\n<div class=\"app\">\n{#key $lang}\n\t<Header />\n{/key}\n```\n\nAnd the result looks like this:\n\n![Screenshot of SvelteKit demo with translated header](/images/translated-demo-header.webp)\n\nThe repo `https://github.com/jldec/paraglide` is public, so anyone can try it.\n\n\n\n\n\n\n", "html": "<h1>i18n with inlang</h1>\n<p>Last week I had the <a href=\"the-web-is-for-everyone.md\">pleasure</a> of being at the Svelte Society London gettogether. The evening started with a talk about i18n by <a href=\"https://twitter.com/loris_sigrist\">@loris_sigrist</a> from <a href=\"https://inlang.com/\">inlang</a>.</p>\n<p>I was impressed by the story of <a href=\"https://inlang.com/m/gerre34r/library-inlang-paraglideJs\">paraglide-js</a> which compiles translatable strings into tree-shakable javascript functions. This can improve both the developer experience, with IntelliSense, and the user experience, with reduced bundle sizes.</p>\n<p>In this post I cover:</p>\n<ol>\n<li>Trying out paraglide-js with the SvelteKit demo app.</li>\n<li>Extracting strings using the inlang VS Code extension.</li>\n<li>Translating messages in the browser using Fink.</li>\n<li>Making the translated messages appear in the SvelteKit demo app.</li>\n</ol>\n<p>All the code and commits are in <a href=\"https://github.com/jldec/paraglide\">https://github.com/jldec/paraglide</a>.</p>\n<h2>Create a new SvelteKit demo app</h2>\n<pre><code class=\"language-sh\">pnpm create svelte@latest paraglide\n\ncd paraglide\npnpm install\ngit init && git add -A && git commit -m init\npnpm dev\n</code></pre>\n<p><img src=\"/images/sveltekit-demo.webp\" alt=\"SvelteKit Demo app screenshot\"></p>\n<h2>Add paraglide-js</h2>\n<p>I followed the inlang <a href=\"https://inlang.com/m/gerre34r/library-inlang-paraglideJs#getting-started\">docs</a> to run paraglide <code>init</code>.</p>\n<p>The CLI detected my uncommited changes and offered me a chance to exit before continuing.</p>\n<pre><code class=\"language-sh\">pnpx @inlang/paraglide-js@latest init\n\n ╭─────────────────────────────────────╮\n │ │\n │ Welcome to inlang Paraglide-JS 🪂 │\n │ │\n ╰─────────────────────────────────────╯\n\nℹ You have uncommitted changes.\n\nPlease commit your changes before initializing inlang Paraglide-JS. Committing outstanding \nchanges ensures that you don't lose any work, and see the changes the paraglide-js init \ncommand introduces.\n\n✔ Do you want to initialize inlang Paraglide-JS without committing your current changes?\nYes\n\nℹ Creating a new inlang project in the current working directory.\n✔ Successfully created a new inlang project.\n✔ Added @inlang/paraglide-js to the devDependencies in package.json.\n✔ Successfully added the compile command to the build step in package.json.\n✔ Are you using VSCode?\nYes\n\n✔ Added the inlang vs code extension to the workspace recommendations.\n\n ╭──────────────────────────────────────────────────────────────────────────────────────╮\n │ │\n │ inlang Paraglide-JS has been set up sucessfully. │\n │ │\n │ 1. Run your install command (npm i, yarn install, etc) │\n │ 2. Run the build script (npm run build, or similar.) │\n │ 3. Done :) Happy paragliding 🪂 │\n │ │\n │ For questions and feedback, visit https://github.com/inlang/monorepo/discussions. │\n │ │\n │ │\n ╰──────────────────────────────────────────────────────────────────────────────────────╯\n</code></pre>\n<p>I then ran the suggested install and build.</p>\n<pre><code class=\"language-sh\">pnpm install\npnpm build\ngit add -A\ngit commit -m 'Add paraglide'\n</code></pre>\n<p>And pushed the commit to a new repo on GitHub.</p>\n<pre><code class=\"language-sh\">git remote add origin https://github.com/jldec/paraglide.git\ngit push\n</code></pre>\n<p>The <a href=\"https://github.com/jldec/paraglide/commit/548a1a85b13435d93bde99368148dd417f77b50c\">commit</a> shows that <code>@inlang/paraglide-js@latest init</code> added paraglide to <code>package.json</code>, and created <code>project.inlang/settings.json</code>.</p>\n<h2>Extract strings with the inlang VS Code extension</h2>\n<p>When I opened the project in VS Code, I noticed the recommended <a href=\"https://inlang.com/m/r7kp499g/app-inlang-ideExtension\">inlang VS Code extension</a>.</p>\n<p>Once this was installed, I used 'Inlang: extract message' in the command palette, to extract "Home" and "About" from <code>src/routes/Header.svelte</code> into <code>/messages/en.json</code>.</p>\n<pre><code class=\"language-html\"><li aria-current={$page.url.pathname === '/' ? 'page' : undefined}>\n <a href="/">{m.home()}</a>\n</li>\n<li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>\n <a href="/about">{m.about()}</a>\n</li>\n</code></pre>\n<p><strong>/messages/en.json</strong></p>\n<pre><code class=\"language-json\">{\n "$schema": "https://inlang.com/schema/inlang-message-format",\n\t"home": "Home",\n\t"about": "About"\n}\n</code></pre>\n<p>Once things are configured properly, the extension will show IntelliSense and translated text for each <code>m.<key>()</code>.</p>\n<p><img src=\"/images/translations-in-editor.webp\" alt=\"Screenshot of VS Code extension showing translated text\"></p>\n<p>I also had to add an import for the <code>m</code> namespace, using a <code>$msgs</code> alias which I created in <code>svelte.config.js</code>.</p>\n<pre><code class=\"language-js\">import * as m from '$msgs';\n</code></pre>\n<pre><code class=\"language-js\">kit: {\n alias: {\n '$msgs': 'src/paraglide/messages.js'\n },\n}\n</code></pre>\n<h2>Translation with the Fink editor</h2>\n<p>Fink makes it easy for translators to work with message files in a git repo, without having to clone the repo or use the git CLI directly.</p>\n<p>You can specify the repo in the URL or using the input field at <a href=\"https://fink.inlang.com/\">https://fink.inlang.com/</a>. Once authenticated and connected to the repo, inlang will rememer your recent projects in the dashboard.</p>\n<p>I found the English messages as extracted, and added <code>de</code> German translations using Fink.</p>\n<p><img src=\"/images/fink-deutsch.webp\" alt=\"Fink screenshot with English messages and German translations\"></p>\n<p>Fink then pushed a <a href=\"https://github.com/jldec/paraglide/commit/98b1b618a4d0badb56bde98bd9a6ec9acd03706a\">commit</a> to the repo in GitHub.</p>\n<p>When I pulled down the changes, the new German messages were in <code>/messages/de.json</code>.</p>\n<p><strong>/messages/de.json</strong></p>\n<pre><code class=\"language-json\">{\n\t"$schema": "https://inlang.com/schema/inlang-message-format",\n\t"home": "Startseite",\n\t"about": "Diese App"\n}\n</code></pre>\n<h2>Show translated messages in the SvelteKit demo app</h2>\n<p>To re-render the page Header when the language changes, I hacked Counter.svelte, to call <code>setLanguageTag()</code> whenever the count toggles beteween odd and even.</p>\n<blockquote>\n<p>[!tip]\nNormally users would change the language URL themselves, or it can be detected from the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language\">browser</a>.</p>\n</blockquote>\n<pre><code class=\"language-ts\">import { setLanguageTag } from '../paraglide/runtime';\n\nlet count = 0;\n$: setLanguageTag(count % 2 ? 'en' : 'de');\n</code></pre>\n<p>I also added a reactive Svelte store called <code>$lang</code> in <code>src/lib/lang.ts</code>.</p>\n<pre><code class=\"language-ts\">import { readable } from 'svelte/store';\nimport { onSetLanguageTag, languageTag } from '../paraglide/runtime';\n\nexport const lang = readable<string>('en', (set) => {\n onSetLanguageTag(set);\n});\n</code></pre>\n<p>The value of the store is used as the key to the re-render the Header in <code>+layout.svelte</code>.</p>\n<pre><code class=\"language-html\"><script>\n\timport Header from './Header.svelte';\n\timport './styles.css';\n\timport { lang } from '$lib/lang';\n</script>\n\n<div class="app">\n{#key $lang}\n\t<Header />\n{/key}\n</code></pre>\n<p>And the result looks like this:</p>\n<p><img src=\"/images/translated-demo-header.webp\" alt=\"Screenshot of SvelteKit demo with translated header\"></p>\n<p>The repo <code>https://github.com/jldec/paraglide</code> is public, so anyone can try it.</p>\n" }