+
Skip to content

Commit 3ed783a

Browse files
committed
feat: add i18n
1 parent 463a280 commit 3ed783a

File tree

13 files changed

+175
-22
lines changed

13 files changed

+175
-22
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
'@typescript-eslint/no-use-before-define': 'off',
88
'@typescript-eslint/no-unused-vars': 'warn',
99
'react/jsx-key': 'off',
10+
'import/namespace': 'off',
1011
},
1112
overrides: [
1213
{

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@
99
"astro", // Enable .astro
1010
"typescript", // Enable .ts
1111
"typescriptreact" // Enable .tsx
12+
],
13+
"i18n-ally.localesPaths": [
14+
"src/locale",
15+
"src/locale/lang"
1216
]
1317
}

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@
7373
"unocss": "^0.50.6",
7474
"vite-plugin-pwa": "^0.14.7"
7575
},
76-
"simple-git-hooks": {
77-
"pre-commit": "pnpm lint-staged"
78-
},
79-
"lint-staged": {
80-
"*": "pnpm lint:fix"
81-
}
76+
"simple-git-hooks": {
77+
"pre-commit": "pnpm lint-staged"
78+
},
79+
"lint-staged": {
80+
"*": "pnpm lint:fix"
81+
}
8282
}

src/components/settings/AppGeneralSettings.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
11
import { For } from 'solid-js'
2+
import { localesOptions } from '@/locale'
3+
import { useI18n } from '@/hooks'
24
import SettingsUIComponent from './SettingsUIComponent'
35
import type { Accessor } from 'solid-js'
46
import type { GeneralSettings } from '@/types/app'
7+
import type { SettingsUI } from '@/types/provider'
58

69
interface Props {
710
settingsValue: Accessor<GeneralSettings>
811
updateSettings: (v: Partial<GeneralSettings>) => void
912
}
1013

11-
const settingsUIList = [
14+
const settingsUIList: SettingsUI[] = [
1215
{
1316
key: 'requestWithBackend',
1417
name: 'Request With Backend',
1518
type: 'toggle',
19+
default: false,
1620
},
17-
] as const
21+
{
22+
key: 'locale',
23+
name: 'Change system language',
24+
type: 'select',
25+
default: 'en',
26+
options: localesOptions,
27+
},
28+
]
1829

1930
export default (props: Props) => {
31+
const { t } = useI18n()
32+
2033
return (
2134
<div class="px-4 py-3 transition-colors border-b border-base">
2235
<h3 class="fi gap-2">
2336
<div class="flex-1 fi gap-1.5 overflow-hidden">
2437
<div class="i-carbon-settings" />
25-
<div class="flex-1 text-sm truncate">General</div>
38+
<div class="flex-1 text-sm truncate">{t('settings.general.title')}</div>
2639
</div>
2740
</h3>
2841
<div class="mt-2 flex flex-col">
@@ -32,9 +45,9 @@ export default (props: Props) => {
3245
<SettingsUIComponent
3346
settings={item}
3447
editing={() => true}
35-
value={() => props.settingsValue()[item.key] || false}
48+
value={() => props.settingsValue()[item.key as keyof GeneralSettings] || false}
3649
setValue={(v) => {
37-
props.updateSettings({ [item.key]: v as boolean })
50+
props.updateSettings({ [item.key]: v })
3851
}}
3952
/>
4053
)

src/hooks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './useCopy'
33
export * from './useClickOutside'
44
export * from './useLargeScreen'
55
export * from './useMobileScreen'
6+
export * from './useDepGet'
7+
export * from './useI18n'

src/hooks/useDepGet.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function useDeepGet(target: any, path: string | string[], defaultValue: any) {
2+
if (!Array.isArray(path) && typeof path !== 'string')
3+
throw new TypeError('path must be string or array')
4+
if (target === null)
5+
return defaultValue
6+
7+
let pathArray = path
8+
if (typeof path === 'string') {
9+
path = path.replace(/\[(\w*)\]/g, '.$1')
10+
path = path.startsWith('.') ? path.slice(1) : path
11+
12+
pathArray = path.split('.')
13+
}
14+
15+
let index = 0
16+
let levelPath: string
17+
while (target !== null && index < pathArray.length) {
18+
levelPath = pathArray[index++]
19+
target = target[levelPath]
20+
}
21+
22+
return index === pathArray.length ? target : defaultValue
23+
}

src/hooks/useI18n.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createSignal } from 'solid-js'
2+
import { en } from '@/locale/lang'
3+
import { locales } from '@/locale'
4+
import { providerSettingsMap } from '@/stores/settings'
5+
import { useDeepGet } from './useDepGet'
6+
import type { Accessor } from 'solid-js'
7+
import type { TranslatePair } from '@/locale'
8+
import type { GeneralSettings } from '@/types/app'
9+
10+
const [currentLocale, setCurrentLocale] = createSignal(en.locales)
11+
12+
export type TranslatorOption = Record<string, string | number>
13+
export type Translator = (path: string, option?: TranslatorOption) => string
14+
export interface I18nContext {
15+
locale: TranslatePair
16+
t: Translator
17+
}
18+
19+
export const translate = (path: string, option: TranslatorOption | undefined) => {
20+
return currentLocale() ? (useDeepGet(currentLocale(), path, path) as string).replace(/\{(\w+)\}/g, (_, key) => `${option?.[key] ?? `{${key}}`}`) : ''
21+
}
22+
23+
export const buildTranslator = (): Translator => (path, option) => translate(path, option)
24+
export const buildI18nContext = (locale: Accessor<TranslatePair>): I18nContext => {
25+
return {
26+
locale: locale(),
27+
t: buildTranslator(),
28+
}
29+
}
30+
31+
export function useI18n() {
32+
let defaultLocale = providerSettingsMap.get()?.general?.locale ?? 'en'
33+
providerSettingsMap.listen((value, changedKey) => {
34+
const general = value[changedKey] as unknown as GeneralSettings
35+
defaultLocale = general?.locale
36+
setCurrentLocale(locales[defaultLocale as string])
37+
})
38+
39+
setCurrentLocale(locales[defaultLocale as string])
40+
return buildI18nContext(currentLocale)
41+
}

src/locale/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Langs from './lang'
2+
import type { SelectOptionType } from '@/types/provider'
3+
4+
export type LanguageType = keyof typeof Langs
5+
6+
export interface TranslatePair {
7+
[key: string]: string | string[] | TranslatePair
8+
}
9+
10+
export interface language {
11+
name: string
12+
desc: string
13+
locales: TranslatePair
14+
}
15+
16+
export const locales = Object.fromEntries(Object.entries(Langs).map(([key, value]) => [key, value.locales]))
17+
18+
export const localesOptions: SelectOptionType[] = Object.entries(Langs).map(([key, value]) => ({ label: value.desc, value: key }))

src/locale/lang/en.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { language } from '..'
2+
3+
export const en = {
4+
name: 'en',
5+
desc: 'English',
6+
locales: {
7+
settings: {
8+
btn: 'TEST',
9+
general: {
10+
title: 'General',
11+
requestWithBackend: 'Request With Backend',
12+
locale: 'Change system language',
13+
},
14+
openai: {
15+
title: 'OpenAI',
16+
key: '',
17+
},
18+
replicate: {},
19+
},
20+
conversations: {
21+
title: 'CONVERSATIONS',
22+
},
23+
},
24+
} as language

src/locale/lang/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './en'
2+
export * from './zh-cn'

0 commit comments

Comments
 (0)
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载