Skip to content

Commit c2dbcf2

Browse files
authored
refactor(i18n): update to @solid-primitives/i18n v2 (#109)
* chore(deps): update to vite@6 * fix(useDownload): correct token parameter handling * chore(deps): update to typescript@5 * chore(deps): update to @solid-primitives/i18n@2 * feat(i18n): update usage to @solid-primitives/i18n@2 * fix(useT): remove log added in dev
1 parent 94eed78 commit c2dbcf2

File tree

9 files changed

+866
-662
lines changed

9 files changed

+866
-662
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,24 @@
4242
"@types/qrcode": "^1.5.5",
4343
"@types/sha256": "^0.2.2",
4444
"@types/streamsaver": "^2.0.5",
45-
"@vitejs/plugin-legacy": "^4.1.1",
45+
"@vitejs/plugin-legacy": "^6.1.1",
4646
"husky": "^9.0.0",
4747
"lint-staged": "^16.0.0",
4848
"prettier": "3.6.2",
4949
"rollup-plugin-copy": "^3.5.0",
50-
"terser": "^5.42.0",
51-
"typescript": "^4.9.5",
52-
"vite": "^4.5.14",
50+
"terser": "^5.43.1",
51+
"typescript": "^5.8.3",
52+
"vite": "^6.3.5",
5353
"vite-plugin-dynamic-base": "^0.4.9",
54-
"vite-plugin-solid": "^2.11.6"
54+
"vite-plugin-solid": "^2.11.7"
5555
},
5656
"dependencies": {
5757
"@egjs/view360": "4.0.0-beta.7",
5858
"@github/webauthn-json": "^2.1.1",
5959
"@hope-ui/solid": "0.6.7",
6060
"@monaco-editor/loader": "^1.5.0",
6161
"@pdfslick/solid": "^3.0.0",
62-
"@solid-primitives/i18n": "^1.4.1",
62+
"@solid-primitives/i18n": "^2.2.1",
6363
"@solid-primitives/keyboard": "^1.3.1",
6464
"@solid-primitives/storage": "^1.3.11",
6565
"@solidjs/router": "^0.9.1",

pnpm-lock.yaml

Lines changed: 749 additions & 564 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/App.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@ import {
1010
Switch,
1111
} from "solid-js"
1212
import { Portal } from "solid-js/web"
13+
import { Error, FullScreenLoading } from "~/components"
1314
import { useLoading, useRouter, useT } from "~/hooks"
14-
import { globalStyles } from "./theme"
15-
import { bus, r, handleRespWithoutAuthAndNotify, base_path } from "~/utils"
1615
import { setSettings } from "~/store"
17-
import { Error, FullScreenLoading } from "~/components"
16+
import { setArchiveExtensions } from "~/store/archive"
17+
import { Resp } from "~/types"
18+
import { base_path, bus, handleRespWithoutAuthAndNotify, r } from "~/utils"
1819
import { MustUser } from "./MustUser"
1920
import "./index.css"
20-
import { useI18n } from "@solid-primitives/i18n"
21-
import { initialLang, langMap, loadedLangs } from "./i18n"
22-
import { PEmptyResp, PResp, Resp } from "~/types"
23-
import { setArchiveExtensions } from "~/store/archive"
21+
import { globalStyles } from "./theme"
2422

2523
const Home = lazy(() => import("~/pages/home/Layout"))
2624
const Manage = lazy(() => import("~/pages/manage"))
@@ -30,7 +28,6 @@ const Test = lazy(() => import("~/pages/test"))
3028
const App: Component = () => {
3129
const t = useT()
3230
globalStyles()
33-
const [, { add }] = useI18n()
3431
const isRouting = useIsRouting()
3532
const { to, pathname } = useRouter()
3633
const onTo = (path: string) => {
@@ -48,10 +45,6 @@ const App: Component = () => {
4845
const [err, setErr] = createSignal<string[]>([])
4946
const [loading, data] = useLoading(() =>
5047
Promise.all([
51-
(async () => {
52-
add(initialLang, (await langMap[initialLang]()).default)
53-
loadedLangs.add(initialLang)
54-
})(),
5548
(async () => {
5649
handleRespWithoutAuthAndNotify(
5750
(await r.get("/public/settings")) as Resp<Record<string, string>>,

src/app/i18n.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,56 @@
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 }
34

4-
interface Language {
5-
code: string
6-
lang: string
7-
}
5+
// glob search by Vite
86
const langs = import.meta.glob("~/lang/*/index.json", {
97
eager: true,
108
import: "lang",
119
})
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()
2120
const defaultLang =
21+
languages.find((lang) => lang.code.toLowerCase() === userLang)?.code ||
2222
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],
2924
)?.code ||
3025
"en"
3126

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)) {
3431
initialLang = defaultLang
3532
}
3633

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+
}
4351
}
4452

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)
5055

51-
export { languages, i18n, currentLang, setLang }
56+
export const [dict] = createResource(currentLang, fetchDictionary)

src/app/index.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { HopeProvider, NotificationsProvider } from "@hope-ui/solid"
2-
import { I18nContext } from "@solid-primitives/i18n"
32
import { ErrorBoundary, Suspense } from "solid-js"
43
import { Error, FullScreenLoading } from "~/components"
54
import App from "./App"
6-
import { i18n } from "./i18n"
75
import { globalStyles, theme } from "./theme"
86

97
const Index = () => {
@@ -16,13 +14,11 @@ const Index = () => {
1614
return <Error msg={`System error: ${err}`} h="100vh" />
1715
}}
1816
>
19-
<I18nContext.Provider value={i18n}>
20-
<NotificationsProvider duration={3000}>
21-
<Suspense fallback={<FullScreenLoading />}>
22-
<App />
23-
</Suspense>
24-
</NotificationsProvider>
25-
</I18nContext.Provider>
17+
<NotificationsProvider duration={3000}>
18+
<Suspense fallback={<FullScreenLoading />}>
19+
<App />
20+
</Suspense>
21+
</NotificationsProvider>
2622
</ErrorBoundary>
2723
</HopeProvider>
2824
)

src/components/SwitchLanguage.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
Spinner,
1010
useColorModeValue,
1111
} from "@hope-ui/solid"
12-
import { useI18n } from "@solid-primitives/i18n"
1312
import { createSignal, For, Show } from "solid-js"
14-
import { langMap, languages, loadedLangs, setLang } from "~/app/i18n"
13+
import { Lang, languages, setCurrentLang } from "~/app/i18n"
1514
// import { TbLanguageHiragana } from "solid-icons/tb";
1615
import { IoLanguageOutline } from "solid-icons/io"
1716
import { Portal } from "solid-js/web"
@@ -21,16 +20,8 @@ const [fetchingLang, setFetchingLang] = createSignal(false)
2120
export const SwitchLanguage = <C extends ElementType = "button">(
2221
props: MenuTriggerProps<C>,
2322
) => {
24-
const [, { locale, add }] = useI18n()
25-
const switchLang = async (lang: string) => {
26-
if (!loadedLangs.has(lang)) {
27-
setFetchingLang(true)
28-
add(lang, (await langMap[lang]()).default)
29-
setFetchingLang(false)
30-
loadedLangs.add(lang)
31-
}
32-
locale(lang)
33-
setLang(lang)
23+
const switchLang = async (lang: Lang) => {
24+
setCurrentLang(lang)
3425
localStorage.setItem("lang", lang)
3526
}
3627

src/hooks/useDownload.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function getSaveDir(rpc_url: string, rpc_secret: string) {
2626
id: Math.random().toString(),
2727
jsonrpc: "2.0",
2828
method: "aria2.getGlobalOption",
29-
params: ["token:" + rpc_secret ?? ""],
29+
params: ["token:" + (rpc_secret ?? "")],
3030
})
3131
console.log(resp)
3232
if (resp.status === 200) {
@@ -140,7 +140,7 @@ export const useDownload = () => {
140140
jsonrpc: "2.0",
141141
method: "aria2.addUri",
142142
params: [
143-
"token:" + aria2_rpc_secret ?? "",
143+
"token:" + (aria2_rpc_secret ?? ""),
144144
[res[key].url],
145145
{
146146
out: res[key].name,

src/hooks/useT.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
1-
import { useI18n } from "@solid-primitives/i18n"
1+
import { dict, i18n, languages } from "~/app/i18n"
22
import { firstUpperCase } from "~/utils"
33

4-
const useT = () => {
5-
const [t] = useI18n()
4+
export const useT = () => {
5+
const t = i18n.translator(dict)
6+
const tt = (key: string, params?: i18n.BaseTemplateArgs) => {
7+
return i18n.resolveTemplate ? i18n.resolveTemplate(key, params) : t(key)
8+
}
69
return (
710
key: string,
8-
params?: Record<string, string> | undefined,
11+
params?: i18n.BaseTemplateArgs | undefined,
912
defaultValue?: string | undefined,
1013
) => {
11-
const value = t(key, params, defaultValue)
12-
if (!value) {
13-
if (import.meta.env.DEV) return key
14-
let lastDotIndex = key.lastIndexOf(".")
15-
if (lastDotIndex === key.length - 1) {
16-
lastDotIndex = key.lastIndexOf(".", lastDotIndex - 1)
17-
}
18-
const last = key.slice(lastDotIndex + 1)
19-
return firstUpperCase(last).split("_").join(" ")
20-
}
21-
return value
14+
const value = params
15+
? (tt(key, params) as string | undefined)
16+
: (t(key) as string | undefined)
17+
18+
if (value) return value
19+
20+
if (defaultValue) return defaultValue
21+
22+
if (import.meta.env.DEV) return key
23+
24+
return formatKeyAsDisplay(key)
2225
}
2326
}
2427

25-
export { useT }
28+
const formatKeyAsDisplay = (key: string): string => {
29+
let lastDotIndex = key.lastIndexOf(".")
30+
if (lastDotIndex === key.length - 1) {
31+
lastDotIndex = key.lastIndexOf(".", lastDotIndex - 1)
32+
}
33+
const last = key.slice(lastDotIndex + 1)
34+
return firstUpperCase(last).split("_").join(" ")
35+
}

src/lang/en/entry.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
1-
const jsons = import.meta.glob("./*.json", { eager: true, import: "default" })
2-
const langs: any = {}
3-
for (const path in jsons) {
4-
const name = path.split("/")[1].split(".")[0]
5-
langs[name] = jsons[path]
1+
import br from "./br.json"
2+
import drivers from "./drivers.json"
3+
import global from "./global.json"
4+
import home from "./home.json"
5+
import index from "./index.json"
6+
import indexes from "./indexes.json"
7+
import login from "./login.json"
8+
import manage from "./manage.json"
9+
import metas from "./metas.json"
10+
import settings_other from "./settings_other.json"
11+
import settings from "./settings.json"
12+
import storages from "./storages.json"
13+
import tasks from "./tasks.json"
14+
import users from "./users.json"
15+
16+
export const dict = {
17+
br,
18+
drivers,
19+
global,
20+
home,
21+
index,
22+
indexes,
23+
login,
24+
manage,
25+
metas,
26+
settings_other,
27+
settings,
28+
storages,
29+
tasks,
30+
users,
631
}
7-
export default langs

0 commit comments

Comments
 (0)