Merge remote-tracking branch 'origin/main' into feat/go-to-anything-v2

This commit is contained in:
yyh
2026-02-09 17:05:21 +08:00
9 changed files with 33 additions and 35 deletions

View File

@@ -38,7 +38,7 @@ const DeprecationNotice: FC<DeprecationNoticeProps> = ({
iconWrapperClassName,
textClassName,
}) => {
const { t } = useTranslation()
const { t } = useTranslation('plugin')
const deprecatedReasonKey = useMemo(() => {
if (!deprecatedReason)

View File

@@ -1,7 +1,7 @@
'use client'
import type { Resource } from 'i18next'
import type { Locale } from '.'
import type { NamespaceCamelCase, NamespaceKebabCase } from './resources'
import type { Namespace, NamespaceInFileName } from './resources'
import { kebabCase } from 'es-toolkit/string'
import { createInstance } from 'i18next'
import resourcesToBackend from 'i18next-resources-to-backend'
@@ -14,7 +14,7 @@ export function createI18nextInstance(lng: Locale, resources: Resource) {
.use(initReactI18next)
.use(resourcesToBackend((
language: Locale,
namespace: NamespaceKebabCase | NamespaceCamelCase,
namespace: NamespaceInFileName | Namespace,
) => {
const namespaceKebab = kebabCase(namespace)
return import(`../i18n/${language}/${namespaceKebab}.json`)

View File

@@ -1,9 +1,9 @@
'use client'
import type { NamespaceCamelCase } from './resources'
import type { Namespace } from './resources'
import { useTranslation as useTranslationOriginal } from 'react-i18next'
export function useTranslation(ns?: NamespaceCamelCase) {
export function useTranslation<T extends Namespace | undefined = undefined>(ns?: T) {
return useTranslationOriginal(ns)
}

View File

@@ -1,13 +1,13 @@
import type { NamespaceCamelCase } from './resources'
import type { Namespace } from './resources'
import { use } from 'react'
import { getLocaleOnServer, getTranslation } from './server'
async function getI18nConfig(ns?: NamespaceCamelCase) {
async function getI18nConfig<T extends Namespace | undefined = undefined>(ns?: T) {
const lang = await getLocaleOnServer()
return getTranslation(lang, ns)
}
export function useTranslation(ns?: NamespaceCamelCase) {
export function useTranslation<T extends Namespace | undefined = undefined>(ns?: T) {
return use(getI18nConfig(ns))
}

View File

@@ -1,4 +1,5 @@
import { kebabCase } from 'es-toolkit/string'
import { kebabCase } from 'string-ts'
import { ObjectKeys } from '@/utils/object'
import appAnnotation from '../i18n/en-US/app-annotation.json'
import appApi from '../i18n/en-US/app-api.json'
import appDebug from '../i18n/en-US/app-debug.json'
@@ -64,19 +65,10 @@ const resources = {
workflow,
}
export type KebabCase<S extends string> = S extends `${infer T}${infer U}`
? T extends Lowercase<T>
? `${T}${KebabCase<U>}`
: `-${Lowercase<T>}${KebabCase<U>}`
: S
export type CamelCase<S extends string> = S extends `${infer T}-${infer U}`
? `${T}${Capitalize<CamelCase<U>>}`
: S
export type Resources = typeof resources
export type NamespaceCamelCase = keyof Resources
export type NamespaceKebabCase = KebabCase<NamespaceCamelCase>
export const namespacesCamelCase = Object.keys(resources) as NamespaceCamelCase[]
export const namespacesKebabCase = namespacesCamelCase.map(ns => kebabCase(ns)) as NamespaceKebabCase[]
export const namespaces = ObjectKeys(resources)
export type Namespace = typeof namespaces[number]
export const namespacesInFileName = namespaces.map(ns => kebabCase(ns))
export type NamespaceInFileName = typeof namespacesInFileName[number]

View File

@@ -1,6 +1,6 @@
import type { i18n as I18nInstance, Resource, ResourceLanguage } from 'i18next'
import type { Locale } from '.'
import type { NamespaceCamelCase, NamespaceKebabCase } from './resources'
import type { Namespace, NamespaceInFileName } from './resources'
import { match } from '@formatjs/intl-localematcher'
import { kebabCase } from 'es-toolkit/compat'
import { camelCase } from 'es-toolkit/string'
@@ -12,7 +12,7 @@ import { cache } from 'react'
import { initReactI18next } from 'react-i18next/initReactI18next'
import { serverOnlyContext } from '@/utils/server-only-context'
import { i18n } from '.'
import { namespacesKebabCase } from './resources'
import { namespacesInFileName } from './resources'
import { getInitOptions } from './settings'
const [getLocaleCache, setLocaleCache] = serverOnlyContext<Locale | null>(null)
@@ -26,8 +26,8 @@ const getOrCreateI18next = async (lng: Locale) => {
instance = createInstance()
await instance
.use(initReactI18next)
.use(resourcesToBackend((language: Locale, namespace: NamespaceCamelCase | NamespaceKebabCase) => {
const fileNamespace = kebabCase(namespace) as NamespaceKebabCase
.use(resourcesToBackend((language: Locale, namespace: Namespace | NamespaceInFileName) => {
const fileNamespace = kebabCase(namespace)
return import(`../i18n/${language}/${fileNamespace}.json`)
}))
.init({
@@ -38,7 +38,7 @@ const getOrCreateI18next = async (lng: Locale) => {
return instance
}
export async function getTranslation(lng: Locale, ns?: NamespaceCamelCase) {
export async function getTranslation<T extends Namespace>(lng: Locale, ns?: T) {
const i18nextInstance = await getOrCreateI18next(lng)
if (ns && !i18nextInstance.hasLoadedNamespace(ns))
@@ -84,7 +84,7 @@ export const getResources = cache(async (lng: Locale): Promise<Resource> => {
const messages = {} as ResourceLanguage
await Promise.all(
(namespacesKebabCase).map(async (ns) => {
(namespacesInFileName).map(async (ns) => {
const mod = await import(`../i18n/${lng}/${ns}.json`)
messages[camelCase(ns)] = mod.default
}),

View File

@@ -1,5 +1,5 @@
import type { InitOptions } from 'i18next'
import { namespacesCamelCase } from './resources'
import { namespaces } from './resources'
export function getInitOptions(): InitOptions {
return {
@@ -8,7 +8,7 @@ export function getInitOptions(): InitOptions {
fallbackLng: 'en-US',
partialBundledLanguages: true,
keySeparator: false,
ns: namespacesCamelCase,
ns: namespaces,
interpolation: {
escapeValue: false,
},

7
web/types/i18n.d.ts vendored
View File

@@ -1,17 +1,16 @@
import type { NamespaceCamelCase, Resources } from '../i18n-config/resources'
import type { Namespace, Resources } from '../i18n-config/resources'
import 'i18next'
declare module 'i18next' {
// eslint-disable-next-line ts/consistent-type-definitions
interface CustomTypeOptions {
defaultNS: 'common'
resources: Resources
keySeparator: false
}
}
export type I18nKeysByPrefix<
NS extends NamespaceCamelCase,
NS extends Namespace,
Prefix extends string = '',
> = Prefix extends ''
? keyof Resources[NS]
@@ -22,7 +21,7 @@ export type I18nKeysByPrefix<
: never
export type I18nKeysWithPrefix<
NS extends NamespaceCamelCase,
NS extends Namespace,
Prefix extends string = '',
> = Prefix extends ''
? keyof Resources[NS]

7
web/utils/object.ts Normal file
View File

@@ -0,0 +1,7 @@
export function ObjectFromEntries<const T extends ReadonlyArray<readonly [PropertyKey, unknown]>>(entries: T): { [K in T[number]as K[0]]: K[1] } {
return Object.fromEntries(entries) as { [K in T[number]as K[0]]: K[1] }
}
export function ObjectKeys<const T extends Record<string, unknown>>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[]
}