Skip to content

Commit

Permalink
feat: add i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
yzh990918 committed May 16, 2023
1 parent 463a280 commit 3ed783a
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 22 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'react/jsx-key': 'off',
'import/namespace': 'off',
},
overrides: [
{
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@
"astro", // Enable .astro
"typescript", // Enable .ts
"typescriptreact" // Enable .tsx
],
"i18n-ally.localesPaths": [
"src/locale",
"src/locale/lang"
]
}
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@
"unocss": "^0.50.6",
"vite-plugin-pwa": "^0.14.7"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "pnpm lint:fix"
}
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "pnpm lint:fix"
}
}
23 changes: 18 additions & 5 deletions src/components/settings/AppGeneralSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { For } from 'solid-js'
import { localesOptions } from '@/locale'
import { useI18n } from '@/hooks'
import SettingsUIComponent from './SettingsUIComponent'
import type { Accessor } from 'solid-js'
import type { GeneralSettings } from '@/types/app'
import type { SettingsUI } from '@/types/provider'

interface Props {
settingsValue: Accessor<GeneralSettings>
updateSettings: (v: Partial<GeneralSettings>) => void
}

const settingsUIList = [
const settingsUIList: SettingsUI[] = [
{
key: 'requestWithBackend',
name: 'Request With Backend',
type: 'toggle',
default: false,
},
] as const
{
key: 'locale',
name: 'Change system language',
type: 'select',
default: 'en',
options: localesOptions,
},
]

export default (props: Props) => {
const { t } = useI18n()

return (
<div class="px-4 py-3 transition-colors border-b border-base">
<h3 class="fi gap-2">
<div class="flex-1 fi gap-1.5 overflow-hidden">
<div class="i-carbon-settings" />
<div class="flex-1 text-sm truncate">General</div>
<div class="flex-1 text-sm truncate">{t('settings.general.title')}</div>
</div>
</h3>
<div class="mt-2 flex flex-col">
Expand All @@ -32,9 +45,9 @@ export default (props: Props) => {
<SettingsUIComponent
settings={item}
editing={() => true}
value={() => props.settingsValue()[item.key] || false}
value={() => props.settingsValue()[item.key as keyof GeneralSettings] || false}
setValue={(v) => {
props.updateSettings({ [item.key]: v as boolean })
props.updateSettings({ [item.key]: v })
}}
/>
)
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './useCopy'
export * from './useClickOutside'
export * from './useLargeScreen'
export * from './useMobileScreen'
export * from './useDepGet'
export * from './useI18n'
23 changes: 23 additions & 0 deletions src/hooks/useDepGet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export function useDeepGet(target: any, path: string | string[], defaultValue: any) {
if (!Array.isArray(path) && typeof path !== 'string')
throw new TypeError('path must be string or array')
if (target === null)
return defaultValue

let pathArray = path
if (typeof path === 'string') {
path = path.replace(/\[(\w*)\]/g, '.$1')
path = path.startsWith('.') ? path.slice(1) : path

pathArray = path.split('.')
}

let index = 0
let levelPath: string
while (target !== null && index < pathArray.length) {
levelPath = pathArray[index++]
target = target[levelPath]
}

return index === pathArray.length ? target : defaultValue
}
41 changes: 41 additions & 0 deletions src/hooks/useI18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createSignal } from 'solid-js'
import { en } from '@/locale/lang'
import { locales } from '@/locale'
import { providerSettingsMap } from '@/stores/settings'
import { useDeepGet } from './useDepGet'
import type { Accessor } from 'solid-js'
import type { TranslatePair } from '@/locale'
import type { GeneralSettings } from '@/types/app'

const [currentLocale, setCurrentLocale] = createSignal(en.locales)

export type TranslatorOption = Record<string, string | number>
export type Translator = (path: string, option?: TranslatorOption) => string
export interface I18nContext {
locale: TranslatePair
t: Translator
}

export const translate = (path: string, option: TranslatorOption | undefined) => {
return currentLocale() ? (useDeepGet(currentLocale(), path, path) as string).replace(/\{(\w+)\}/g, (_, key) => `${option?.[key] ?? `{${key}}`}`) : ''
}

export const buildTranslator = (): Translator => (path, option) => translate(path, option)
export const buildI18nContext = (locale: Accessor<TranslatePair>): I18nContext => {
return {
locale: locale(),
t: buildTranslator(),
}
}

export function useI18n() {
let defaultLocale = providerSettingsMap.get()?.general?.locale ?? 'en'
providerSettingsMap.listen((value, changedKey) => {
const general = value[changedKey] as unknown as GeneralSettings
defaultLocale = general?.locale
setCurrentLocale(locales[defaultLocale as string])
})

setCurrentLocale(locales[defaultLocale as string])
return buildI18nContext(currentLocale)
}
18 changes: 18 additions & 0 deletions src/locale/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Langs from './lang'
import type { SelectOptionType } from '@/types/provider'

export type LanguageType = keyof typeof Langs

export interface TranslatePair {
[key: string]: string | string[] | TranslatePair
}

export interface language {
name: string
desc: string
locales: TranslatePair
}

export const locales = Object.fromEntries(Object.entries(Langs).map(([key, value]) => [key, value.locales]))

export const localesOptions: SelectOptionType[] = Object.entries(Langs).map(([key, value]) => ({ label: value.desc, value: key }))
24 changes: 24 additions & 0 deletions src/locale/lang/en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { language } from '..'

export const en = {
name: 'en',
desc: 'English',
locales: {
settings: {
btn: 'TEST',
general: {
title: 'General',
requestWithBackend: 'Request With Backend',
locale: 'Change system language',
},
openai: {
title: 'OpenAI',
key: '',
},
replicate: {},
},
conversations: {
title: 'CONVERSATIONS',
},
},
} as language
2 changes: 2 additions & 0 deletions src/locale/lang/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './en'
export * from './zh-cn'
24 changes: 24 additions & 0 deletions src/locale/lang/zh-cn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { language } from '..'

export const zhCN = {
name: 'zhCN',
desc: '简体中文',
locales: {
settings: {
btn: 'TEST',
general: {
title: '通用',
requestWithBackend: '请求代理后端',
locale: '切换语言',
},
openai: {
title: 'OpenAI',
key: '',
},
replicate: {},
},
conversations: {
title: 'CONVERSATIONS',
},
},
} as language
22 changes: 11 additions & 11 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import BuildStores from '@/components/client-only/BuildStores'
---

<Layout title="Anse">
<div class="h-100dvh w-screen flex">
<Sidebar direction="left" class="hidden md:block">
<ConversationSidebar client:load />
</Sidebar>
<Main />
<Sidebar direction="right" class="hidden lg:block">
<Settings client:only />
</Sidebar>
</div>
<ModalsLayer client:only />
<BuildStores client:only />
<div class="h-100dvh w-screen flex">
<Sidebar direction="left" class="hidden md:block">
<ConversationSidebar client:load />
</Sidebar>
<Main />
<Sidebar direction="right" class="hidden lg:block">
<Settings client:only />
</Sidebar>
</div>
<ModalsLayer client:only />
<BuildStores client:only />
</Layout>
1 change: 1 addition & 0 deletions src/types/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { SettingsUI } from './provider'
export interface GeneralSettings {
/** Default request directly, can choose to request via proxy */
requestWithBackend: boolean
locale: string
}

export interface BotMeta {
Expand Down

0 comments on commit 3ed783a

Please sign in to comment.