Skip to content

Commit 0f53636

Browse files
authored
Merge pull request #49 from CharlBest/basic-multi-language-support-i18n
Basic language support (i18n)
2 parents ca380d2 + 60d0b3b commit 0f53636

File tree

8 files changed

+189
-1
lines changed

8 files changed

+189
-1
lines changed

‎src/lib/i18n.ts‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { derived, writable } from "svelte/store"
2+
3+
const translations = import.meta.glob<{ default: { [key: string]: string } }>(
4+
`../translation/*.json`,
5+
{ eager: true },
6+
)
7+
const localesArray: string[] = []
8+
Object.entries(translations).map(([path]) => {
9+
const from = "/translation/"
10+
const localeFileName = path.slice(
11+
path.indexOf(from) + from.length,
12+
path.lastIndexOf(".json"),
13+
)
14+
localesArray.push(localeFileName)
15+
})
16+
17+
const path = "../translation"
18+
export const defaultLang = "en"
19+
export const currentLang = writable(defaultLang)
20+
export const langs = localesArray
21+
22+
function translate(
23+
currentLang: string,
24+
key: string,
25+
vars: { [key: string]: string },
26+
returnFallback: boolean,
27+
) {
28+
if (!key) throw new Error("no key provided to $t()")
29+
30+
let text = translations[`${path}/${currentLang}.json`].default[key]
31+
32+
if (!currentLang) throw new Error(`no translation for key "${key}"`)
33+
34+
if (!text) {
35+
if (translations[`${path}/${currentLang}.json`].default[key] == undefined) {
36+
if (
37+
translations[`${path}/${defaultLang}.json`].default[key] == undefined
38+
) {
39+
return key
40+
} else if (returnFallback === false) {
41+
return key
42+
} else {
43+
console.warn(
44+
`"${currentLang}.${key}" translation not found. Showing "${defaultLang}.${key}" instead.`,
45+
)
46+
return translations[`${path}/${defaultLang}.json`].default[key]
47+
}
48+
}
49+
}
50+
51+
Object.keys(vars).map((k) => {
52+
const regex = new RegExp(`{{${k}}}`, "g")
53+
text = text.replace(regex, vars[k])
54+
})
55+
56+
return text
57+
}
58+
59+
export const t = derived(
60+
currentLang,
61+
($currentLang) =>
62+
(key: string, vars = {}, lang = $currentLang, returnFallback = true) =>
63+
translate(lang, key, vars, returnFallback),
64+
)
65+
66+
export const setLang = (lang: string | null, replaceQuery = true) => {
67+
if (!lang || !langs.includes(lang)) {
68+
return null
69+
}
70+
currentLang.set(lang)
71+
if (replaceQuery) {
72+
const url = new URL(window.location.toString())
73+
if (lang) {
74+
url.searchParams.set(encodeURIComponent("lang"), encodeURIComponent(lang))
75+
} else {
76+
url.searchParams.delete("lang")
77+
}
78+
history.replaceState({}, "", url)
79+
}
80+
localStorage.setItem("lang", lang)
81+
document.documentElement.setAttribute("lang", lang)
82+
// set direction
83+
document.documentElement.setAttribute(
84+
"dir",
85+
translations[`${path}/${lang}.json`].default["__direction"],
86+
)
87+
}

‎src/lib/translate.svelte‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
import { t } from "$lib/i18n"
3+
export let k: string
4+
export let v = {}
5+
</script>
6+
7+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
8+
{@html $t(k, v)}

‎src/routes/(marketing)/+layout.svelte‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script>
22
import "../../app.css"
3+
import LangChange from "./lang-change.svelte"
34
</script>
45

56
<div class="navbar bg-base-100 container mx-auto">
@@ -17,6 +18,7 @@
1718
<li class="md:mx-2"><a href="/blog">Blog</a></li>
1819
<li class="md:mx-2"><a href="/pricing">Pricing</a></li>
1920
<li class="md:mx-2"><a href="/account">Account</a></li>
21+
<li class="md:mx-2"><LangChange /></li>
2022
</ul>
2123
<div class="dropdown dropdown-end sm:hidden">
2224
<!-- svelte-ignore a11y-label-has-associated-control -->

‎src/routes/(marketing)/+page.svelte‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { WebsiteName } from "./../../config"
3+
import T from "$lib/translate.svelte"
34
45
const features = [
56
{
@@ -229,7 +230,7 @@
229230
href="https://github.com/CriticalMoments/CMSaasStarter/tree/main#saas-starter"
230231
>
231232
<button class="btn btn-outline btn-primary btn-sm px-6 mt-3 mx-2"
232-
>Read the Docs</button
233+
><T k="Read the Docs" /></button
233234
>
234235
</a>
235236
</div>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<script>
2+
import { currentLang, langs, setLang } from "$lib/i18n"
3+
import { t } from "$lib/i18n"
4+
</script>
5+
6+
<div title="Change Language" class="dropdown dropdown-end">
7+
<div tabindex="0" role="button" class="flex items-center">
8+
<svg
9+
class="h-5 w-5 fill-current"
10+
xmlns="http://www.w3.org/2000/svg"
11+
width="20"
12+
height="20"
13+
viewBox="0 0 512 512"
14+
>
15+
<path
16+
d="M363,176,246,464h47.24l24.49-58h90.54l24.49,58H480ZM336.31,362,363,279.85,389.69,362Z"
17+
/>
18+
<path
19+
d="M272,320c-.25-.19-20.59-15.77-45.42-42.67,39.58-53.64,62-114.61,71.15-143.33H352V90H214V48H170V90H32v44H251.25c-9.52,26.95-27.05,69.5-53.79,108.36-32.68-43.44-47.14-75.88-47.33-76.22L143,152l-38,22,6.87,13.86c.89,1.56,17.19,37.9,54.71,86.57.92,1.21,1.85,2.39,2.78,3.57-49.72,56.86-89.15,79.09-89.66,79.47L64,368l23,36,19.3-11.47c2.2-1.67,41.33-24,92-80.78,24.52,26.28,43.22,40.83,44.3,41.67L255,362Z"
20+
/>
21+
</svg>
22+
23+
<svg
24+
width="12px"
25+
height="12px"
26+
class="hidden h-2 w-2 fill-current opacity-60 sm:inline-block"
27+
xmlns="http://www.w3.org/2000/svg"
28+
viewBox="0 0 2048 2048"
29+
>
30+
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
31+
</svg>
32+
</div>
33+
<div
34+
class="dropdown-content bg-base-200 text-base-content rounded-box top-px mt-16 max-h-[calc(100vh-10rem)] w-56 overflow-y-auto border border-white/5 shadow-2xl outline outline-1 outline-black/5"
35+
>
36+
<ul class="menu menu-sm gap-1">
37+
{#each langs as langItem}
38+
{#if $t("__name", {}, langItem, false) !== "__name"}
39+
<li>
40+
<button
41+
class:active={$currentLang == langItem}
42+
on:click={() => setLang(langItem)}
43+
>
44+
{#if $t("__code", {}, langItem, false) !== "__code"}
45+
<span
46+
class="badge badge-sm badge-outline !pl-1.5 !pr-1 pt-px font-mono !text-[.6rem] font-bold tracking-widest opacity-50"
47+
>
48+
{$t("__code", {}, langItem)}
49+
</span>
50+
{/if}
51+
<span class="font-[sans-serif]">{$t("__name", {}, langItem)}</span
52+
>
53+
{#if $t("__status", {}, langItem) !== "__status" && $t("__status", {}, langItem) !== ""}
54+
<span class="badge badge-sm badge-ghost">
55+
{$t("__status", {}, langItem)}
56+
</span>
57+
{/if}
58+
</button>
59+
</li>
60+
{/if}
61+
{/each}
62+
</ul>
63+
</div>
64+
</div>

‎src/routes/+layout.svelte‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
import { navigating } from "$app/stores"
44
import { expoOut } from "svelte/easing"
55
import { slide } from "svelte/transition"
6+
import { onMount } from "svelte"
7+
import { setLang } from "$lib/i18n"
8+
9+
onMount(() => {
10+
let lang = new URL(document.location.toString()).searchParams.get("lang")
11+
setLang(lang, false)
12+
if (localStorage.getItem("lang")) {
13+
setLang(localStorage.getItem("lang"), false)
14+
}
15+
})
616
</script>
717

818
{#if $navigating}

‎src/translation/af.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"__name": "Afrikaans",
3+
"__code": "AF",
4+
"__direction": "ltr",
5+
"__status": "",
6+
"": "",
7+
"Read the Docs": "Lees die dokumentasie"
8+
}

‎src/translation/en.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"__name": "English",
3+
"__code": "EN",
4+
"__direction": "ltr",
5+
"__status": "",
6+
"": "",
7+
"Read the Docs": "Read the Docs"
8+
}

0 commit comments

Comments
 (0)