I18N support #22

Merged
Rubicon merged 5 commits from feat-i18n into master 2024-09-27 06:20:32 +00:00
6 changed files with 58 additions and 32 deletions
Showing only changes of commit c49ae6fe0a - Show all commits

View file

@ -35,7 +35,7 @@ export function autoMatchLangTag() {
return match(Array.from(navigator.languages), SUPPORTED_LANGS, DEFAULT_LANG);
}
const DateFnLocaleCx = createContext<Accessor<Locale>>(() => enGB);
const DateFnLocaleCx = /* __@PURE__ */createContext<Accessor<Locale>>(() => enGB);
const cachedDateFnLocale: Record<string, Locale> = {
enGB,
@ -149,11 +149,20 @@ export function useLanguage() {
return () => settings().language || autoMatchLangTag();
}
type ImportFn<T> = (name: string) => Promise<{default: T}>
Rubicon marked this conversation as resolved Outdated

That's a needed feature: merging multiple resource into one.
Like:

createStringResource((code) => import(`./i18n/${code}.json`), (code) => import(`./i18n/${code}.js`))

Such feature can help us to reuse some common strings.

That's a needed feature: merging multiple resource into one. Like: ```js createStringResource((code) => import(`./i18n/${code}.json`), (code) => import(`./i18n/${code}.js`)) ``` Such feature can help us to reuse some common strings.
type ImportedModule<F> = F extends ImportFn<infer T> ? T: never
type MergedImportedModule<T> =
T extends [] ? {} :
T extends [infer I] ? ImportedModule<I> :
T extends [infer I, ...infer J] ? ImportedModule<I> & MergedImportedModule<J> : never
export function createStringResource<
M extends Record<string, string | Template<any>>,
>(importFn: (code: string) => Promise<{ default: M }>) {
T extends ImportFn<Record<string, string | Template<any> | undefined>>[],
>(...importFns: T) {
const language = useLanguage();
const cache: Record<string, M | undefined> = {};
const cache: Record<string, MergedImportedModule<T>> = {};
return createResource(
() => [language()] as const,
@ -162,9 +171,13 @@ export function createStringResource<
return cache[nlang];
}
const { default: dict } = await importFn(`${nlang}`);
const results = await Promise.all(importFns.map(x => x(nlang).then(v => v.default)))
return dict;
const merged: MergedImportedModule<T> = Object.assign({}, ...results)
cache[nlang] = merged;
return merged;
},
);
}

View file

@ -17,6 +17,7 @@ import {
} from "@suid/material";
import {
Close as CloseIcon,
Logout,
Public as PublicIcon,
Refresh as RefreshIcon,
Translate as TranslateIcon,
@ -38,11 +39,19 @@ import {
SUPPORTED_REGIONS,
useDateFnLocale,
} from "../platform/i18n.jsx";
import { resolveTemplate, translator } from "@solid-primitives/i18n";
import { resolveTemplate, translator, type Template } from "@solid-primitives/i18n";
type Strings = {
["lang.auto"]: Template<{detected: string}>
} & Record<string, string | undefined>
Rubicon marked this conversation as resolved Outdated

I hope we can use one call to create this instead of two. This use will be very common.

I hope we can use one call to create this instead of two. This use will be very common.
const Settings: ParentComponent = () => {
const [strings] = createStringResource(
(code) => import(`./i18n/${code}.json`),
(code) =>
import(`./i18n/${code}.json`) as Promise<{
default: Strings;
}>,
() => import(`./i18n/lang-names.json`)
);
const t = translator(strings, resolveTemplate);
const navigate = useNavigate();
@ -87,31 +96,34 @@ const Settings: ParentComponent = () => {
<li>
<ul>
<ListSubheader>{t("Accounts")}</ListSubheader>
<ListItem>
<ListItemButton disabled>
<ListItemText>{t("All Notifications")}</ListItemText>
<ListItemSecondaryAction>
<Switch value={false} />
<Switch value={false} disabled/>
</ListItemSecondaryAction>
</ListItem>
</ListItemButton>
<Divider />
<ListItem>
<ListItemButton disabled>
<ListItemText>{t("Sign in...")}</ListItemText>
</ListItem>
</ListItemButton>
<Divider />
</ul>
<For each={profiles()}>
{({ account: acct, inf }) => (
<ul data-site={acct.site} data-username={inf?.username}>
<ListSubheader>{`@${inf?.username ?? "..."}@${new URL(acct.site).host}`}</ListSubheader>
<ListItem>
<ListItemText>Notifications</ListItemText>
<ListItemButton disabled>
<ListItemText>{t("Notifications")}</ListItemText>
<ListItemSecondaryAction>
<Switch value={false} />
<Switch value={false} disabled/>
</ListItemSecondaryAction>
</ListItem>
</ListItemButton>
<Divider />
<ListItemButton onClick={[doSignOut, acct]}>
<ListItemText>Sign out</ListItemText>
<ListItemIcon>
<Logout/>
</ListItemIcon>
<ListItemText>{t("Sign out")}</ListItemText>
</ListItemButton>
<Divider />
</ul>
@ -120,11 +132,7 @@ const Settings: ParentComponent = () => {
</li>
<li>
<ListSubheader>{t("Reading")}</ListSubheader>
<ListItem>
<ListItemText secondary="Regular">Fonts</ListItemText>
</ListItem>
<Divider />
<ListItem
<ListItemButton
onClick={(e) =>
$settings.setKey(
"prefetchTootsDisabled",
@ -138,7 +146,7 @@ const Settings: ParentComponent = () => {
<ListItemSecondaryAction>
<Switch checked={!settings$().prefetchTootsDisabled} />
</ListItemSecondaryAction>
</ListItem>
</ListItemButton>
<Divider />
</li>
<li>
@ -162,7 +170,7 @@ const Settings: ParentComponent = () => {
>
<option value={"xauto"}>
{t("lang.auto", {
detected: t("lang." + autoMatchLangTag()),
detected: t("lang." + autoMatchLangTag()) ?? autoMatchLangTag(),
})}
</option>
<For each={SUPPORTED_LANGS}>
@ -191,7 +199,7 @@ const Settings: ParentComponent = () => {
>
<option value={"xauto"}>
{t("region.auto", {
detected: t("region." + autoMatchRegion()),
detected: t("region." + autoMatchRegion()) ?? autoMatchRegion(),
})}
</option>
<For each={SUPPORTED_REGIONS}>

View file

@ -15,11 +15,11 @@
"Language": "Language",
"Region": "Region",
"lang.auto": "Auto({{detected}})",
"lang.zh-Hans": "中文(简体)",
"lang.en": "English",
"region.auto": "Auto({{detected}})",
"region.en_GB": "Great Britan (English)",
"region.en_US": "United States (English)",
"region.zh_CN": "China Mainland (Chinese)",
"datefmt": "yyyy/MM/dd"
"datefmt": "yyyy/MM/dd",
"Sign out": "Sign out",
"Notifications": "Notifications"
}

View file

@ -0,0 +1,4 @@
{
"lang.zh-Hans": "中文(简体)",
"lang.en": "English"
}

View file

@ -15,11 +15,11 @@
"Language": "语言",
"Region": "区域",
"lang.auto": "自动({{detected}}",
"lang.zh-Hans": "中文(简体)",
"lang.en": "English",
"region.auto": "自动({{detected}}",
"region.en_GB": "英国和苏格兰(英语)",
"region.en_US": "美国(英语)",
"region.zh_CN": "中国大陆(中文)",
"datefmt": "yyyy年MM月dd日"
"datefmt": "yyyy年MM月dd日",
"Sign out": "登出此账户",
"Notifications": "通知"
}

View file

@ -11,5 +11,6 @@
"types": ["vite/client", "vite-plugin-pwa/solid"],
"noEmit": true,
"isolatedModules": true,
"resolveJsonModule": true,
}
}