A library to easily handle content and localization in SvelteKit.
The detection of locales in the url is based on the list provided by unicode in their github repo: https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/availableLocales.json
yarn
or npm install
chmod u+x *.sh
./build-and-move.sh
With the last step you will be able to test the package in a real live scenario.
yarn add @frontline-hq/sveltekit-i18n
or
npm install @frontline-hq/sveltekit-i18n
0 - Install other necessary packages:
yarn add --dev @mdx-js/rollup @frontline-hq/recma-sections @frontline-hq/rollup-merge-import
or
npm install --save-dev @mdx-js/rollup @frontline-hq/recma-sections @frontline-hq/rollup-merge-import
1 - Adjust your vite config to include an mdx loader that supports sections and our rollup plugin for "merge imports":
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
import mdx from '@mdx-js/rollup';
import recmaSection from '@frontline-hq/recma-sections';
import rollupMergeImport from '@frontline-hq/rollup-merge-import';
import { rollupI18N } from '@frontline-hq/sveltekit-i18n';
function getComment(comment: string) {
return comment
? comment.trim().startsWith('c:')
? comment.trim().slice(2)
: undefined
: undefined;
}
const config: UserConfig = {
plugins: [
sveltekit(),
mdx({
jsxImportSource: 'preact',
recmaPlugins: [[recmaSection, { getComment: getComment }]]
}),
rollupMergeImport(),
rollupI18N()
],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
};
export default config;
2 - Setup your locale matching in src/params/lang.ts
to return only the correct locales. This will make sure that the locales within the url of your webapp have to match the array you specified. If a user wants to visit a page on a non-specified locale, this will return an error.
export { match } from '@frontline-hq/sveltekit-i18n';
3 - Wrap your app with the LangRouter svelte component on the root +layout.svelte
component to enable automatic route correction based on the users preferences:
/src/routes/+layout.svelte:
<script lang="ts">
import { LangRouter } from '@frontline-hq/sveltekit-i18n';
</script>
<LangRouter>
<slot />
</LangRouter>
This package populates the data returned from a layout's or page's load()
function to contain the corresponding mdx files contents.
It exposes seven exports and one svelte store with three properties:
// init(), a function to populate your pages data with contents (callable in page / layout load() function)
export async function init({
lang, // Current page lang, pass from load() params
pathname, // Current page pathname, pass from load() params
pathDel = '_', // Delimiter with which fs paths are split upon merge-import
layout = true, // Should layout data also be populated?
page // Should page data be populated?
}: {
lang: string | undefined;
pathname: string;
pathDel?: string;
layout?: boolean;
page?: boolean;
}): Record<string, unknown> {
//...
}
/** i18n, a svelte store that has the following properties:
*
* get(), a function to retrieve populated paths for content
* Takes an id as param.
* - For page data this param should be the lodash path for accessing the mdx files named export directly
* - For layout data, this param still has to include the path (with "_" delimiters) with "_layout" at the end, since there can be multiple layout files applicable to one page / layout
*
* returns the requested piece of data.
*
* lang, a variable that contains the currently active lang.
* Falls back to default lang, if route does not contain a lang.
*
* redirect(), corrects the route based on the users preferences
*/
const i18n: Readable<{
get: (id: string, pathDel?: string) => any;
lang: string;
redirect: () => Promise<void>;
}>
/* setLangPref(), sets the users lang preference in localstorage (and redirects afterwards).
*/
export function setLangPref(lang: string) {}
/* getLangPref(), returns the users language preference based on browser settings and localstorage (localstorage takes precedence)
*/
export function getLangPref(lang: string) {}
/* match(), the matcher function required by sveltekit in the /params/lang.ts file.
*/
export function match() {
}
/* rollupI18NPlugin(), the rollup plugin that amongst other things also reads the i18n config file and exposes it to this i18n library for usage.
*/
export function rollupI18N() {
}
/* Wrapper component to wrap your sveltekit app with that automatically corrects the route based on the users lang preferences.
*/
export class LangRouter //...
// Default export. Same options, but returns a svelte component that directly renders the requested piece of information (utilizing preact)
export default //...
Let's consider the following folder structure for the following explanations:
src/
contents/
en/
page.mdx (export both "default" and "one")
layout.mdx
folder1/
page.mdx
layout.mdx
de/
...
...
routes/
[[lang=lang]]
+layout.svelte
+page.svelte
+page.ts
folder1/
+layout.svelte
+page.ts
+page.svelte
We'll go through some scenarios to understand page exports:
default
export and log named export key
from page.mdx in page /en/folder1:Populate contents with the init function in load()
:
// code of .../folder1/+page.ts
import type { PageLoad } from './$types';
import { init } from '@frontline-hq/sveltekit-i18n';
export const load = (async ({ params: { lang }, url: { pathname } }) => {
const contents = await init({ lang, pathname });
return { contents };
}) satisfies PageLoad;
... and use the contents within the page:
<script lang=ts>
// code of .../folder1/+page.svelte
import Content, { i18n } from '@frontline-hq/sveltekit-i18n';
console.log($i18n.get("key"))
</script>
<Content id="default">
default
export from page.mdx in page /en/:Populate contents with the init function in load()
:
// code of .../+page.ts
import type { PageLoad } from './$types';
import { init } from '@frontline-hq/sveltekit-i18n';
export const load = (async ({ params: { lang }, url: { pathname } }) => {
const contents = await init({ lang, pathname });
return { contents };
}) satisfies PageLoad;
... and use the contents within the page:
<script lang=ts>
// code of .../+page.svelte
import Content from '@frontline-hq/sveltekit-i18n';
</script>
<Content id="default">
I think you can see a pattern arising here now.
Layout contents are the contents for +layout.svelte
pages. They also inherit the contents that their ancestors receive from the /src/contents
directory.
default
exports from both layout.mdx in layout /en/folder1/layout and /en/layout:Populate contents with the init function in load()
:
// code of .../folder1/+layout.ts
import type { LayoutLoad } from './$types';
import { init } from '@frontline-hq/sveltekit-i18n';
export const load = (async ({ params: { lang }, url: { pathname } }) => {
const contents = await init({ lang, pathname });
return { contents };
}) satisfies LayoutLoad;
... and use the contents within the layout:
<script lang=ts>
// code of .../folder1/+layout.svelte
import Content from '@frontline-hq/sveltekit-i18n';
</script>
<!-- This layout inherits the contents from .../+layout.svelte! -->
<Content id="folder1_layout.default">
<Content id="layout.default">