|
1 | | -import { createI18nContext } from "@solid-primitives/i18n" |
2 | | -import { createSignal } from "solid-js" |
| 1 | +import * as i18n from "@solid-primitives/i18n" |
| 2 | +import { createResource, createSignal } from "solid-js" |
| 3 | +export { i18n } |
3 | 4 |
|
4 | | -interface Language { |
5 | | - code: string |
6 | | - lang: string |
7 | | -} |
| 5 | +// glob search by Vite |
8 | 6 | const langs = import.meta.glob("~/lang/*/index.json", { |
9 | 7 | eager: true, |
10 | 8 | import: "lang", |
11 | 9 | }) |
12 | | -const languages: Language[] = [] |
13 | | - |
14 | | -for (const path in langs) { |
15 | | - const name = path.split("/")[3] |
16 | | - languages.push({ |
17 | | - code: name, |
18 | | - lang: langs[path] as string, |
19 | | - }) |
20 | | -} |
| 10 | + |
| 11 | +// all available languages |
| 12 | +export const languages = Object.keys(langs).map((langPath) => { |
| 13 | + const langCode = langPath.split("/")[3] |
| 14 | + const langName = langs[langPath] as string |
| 15 | + return { code: langCode, lang: langName } |
| 16 | +}) |
| 17 | + |
| 18 | +// determine browser's default language |
| 19 | +const userLang = navigator.language.toLowerCase() |
21 | 20 | const defaultLang = |
| 21 | + languages.find((lang) => lang.code.toLowerCase() === userLang)?.code || |
22 | 22 | languages.find( |
23 | | - (lang) => lang.code.toLowerCase() === navigator.language.toLowerCase(), |
24 | | - )?.code || |
25 | | - languages.find( |
26 | | - (lang) => |
27 | | - lang.code.toLowerCase().split("-")[0] === |
28 | | - navigator.language.toLowerCase().split("-")[0], |
| 23 | + (lang) => lang.code.toLowerCase().split("-")[0] === userLang.split("-")[0], |
29 | 24 | )?.code || |
30 | 25 | "en" |
31 | 26 |
|
32 | | -export let initialLang = localStorage.getItem("lang") ?? "" |
33 | | -if (!initialLang || !languages.find((lang) => lang.code === initialLang)) { |
| 27 | +// Get initial language from localStorage or fallback to defaultLang |
| 28 | +export let initialLang = localStorage.getItem("lang") ?? defaultLang |
| 29 | + |
| 30 | +if (!languages.some((lang) => lang.code === initialLang)) { |
34 | 31 | initialLang = defaultLang |
35 | 32 | } |
36 | 33 |
|
37 | | -// store lang and import |
38 | | -export const langMap: Record<string, any> = {} |
39 | | -const imports = import.meta.glob("~/lang/*/entry.ts") |
40 | | -for (const path in imports) { |
41 | | - const name = path.split("/")[3] |
42 | | - langMap[name] = imports[path] |
| 34 | +// Type imports |
| 35 | +// use `type` to not include the actual dictionary in the bundle |
| 36 | +import type * as en from "~/lang/en/entry" |
| 37 | + |
| 38 | +export type Lang = keyof typeof langs |
| 39 | +export type RawDictionary = typeof en.dict |
| 40 | +export type Dictionary = i18n.Flatten<RawDictionary> |
| 41 | + |
| 42 | +// Fetch and flatten the dictionary |
| 43 | +const fetchDictionary = async (locale: Lang): Promise<Dictionary> => { |
| 44 | + try { |
| 45 | + const dict: RawDictionary = (await import(`~/lang/${locale}/entry.ts`)).dict |
| 46 | + return i18n.flatten(dict) // Flatten dictionary for easier access to keys |
| 47 | + } catch (err) { |
| 48 | + console.error(`Error loading dictionary for locale: ${locale}`, err) |
| 49 | + throw new Error(`Failed to load dictionary for ${locale}`) |
| 50 | + } |
43 | 51 | } |
44 | 52 |
|
45 | | -export const loadedLangs = new Set<string>() |
46 | | - |
47 | | -const i18n = createI18nContext({}, initialLang) |
48 | | - |
49 | | -const [currentLang, setLang] = createSignal(initialLang) |
| 53 | +// Signals to track current language and dictionary state |
| 54 | +export const [currentLang, setCurrentLang] = createSignal<Lang>(initialLang) |
50 | 55 |
|
51 | | -export { languages, i18n, currentLang, setLang } |
| 56 | +export const [dict] = createResource(currentLang, fetchDictionary) |
0 commit comments