diff --git a/web/app/components/plugins/base/deprecation-notice.tsx b/web/app/components/plugins/base/deprecation-notice.tsx index 513b27a2cf..01b37bc20c 100644 --- a/web/app/components/plugins/base/deprecation-notice.tsx +++ b/web/app/components/plugins/base/deprecation-notice.tsx @@ -38,7 +38,7 @@ const DeprecationNotice: FC = ({ iconWrapperClassName, textClassName, }) => { - const { t } = useTranslation() + const { t } = useTranslation('plugin') const deprecatedReasonKey = useMemo(() => { if (!deprecatedReason) diff --git a/web/i18n-config/client.ts b/web/i18n-config/client.ts index 17d3dceae1..efee2ce853 100644 --- a/web/i18n-config/client.ts +++ b/web/i18n-config/client.ts @@ -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`) diff --git a/web/i18n-config/lib.client.ts b/web/i18n-config/lib.client.ts index fffb4d95ae..501737b274 100644 --- a/web/i18n-config/lib.client.ts +++ b/web/i18n-config/lib.client.ts @@ -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(ns?: T) { return useTranslationOriginal(ns) } diff --git a/web/i18n-config/lib.server.ts b/web/i18n-config/lib.server.ts index 4727ed482f..6136954296 100644 --- a/web/i18n-config/lib.server.ts +++ b/web/i18n-config/lib.server.ts @@ -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(ns?: T) { const lang = await getLocaleOnServer() return getTranslation(lang, ns) } -export function useTranslation(ns?: NamespaceCamelCase) { +export function useTranslation(ns?: T) { return use(getI18nConfig(ns)) } diff --git a/web/i18n-config/resources.ts b/web/i18n-config/resources.ts index 4bcfb98e14..db5aa5658c 100644 --- a/web/i18n-config/resources.ts +++ b/web/i18n-config/resources.ts @@ -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 `${infer T}${infer U}` - ? T extends Lowercase - ? `${T}${KebabCase}` - : `-${Lowercase}${KebabCase}` - : S - -export type CamelCase = S extends `${infer T}-${infer U}` - ? `${T}${Capitalize>}` - : S - export type Resources = typeof resources -export type NamespaceCamelCase = keyof Resources -export type NamespaceKebabCase = KebabCase -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] diff --git a/web/i18n-config/server.ts b/web/i18n-config/server.ts index 403040c134..d9c0501d2d 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n-config/server.ts @@ -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(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(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 => { 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 }), diff --git a/web/i18n-config/settings.ts b/web/i18n-config/settings.ts index ea2a8a0058..accbc1600d 100644 --- a/web/i18n-config/settings.ts +++ b/web/i18n-config/settings.ts @@ -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, }, diff --git a/web/types/i18n.d.ts b/web/types/i18n.d.ts index bdf2ef56d3..819e02a43d 100644 --- a/web/types/i18n.d.ts +++ b/web/types/i18n.d.ts @@ -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] diff --git a/web/utils/object.ts b/web/utils/object.ts new file mode 100644 index 0000000000..cf5d718ff2 --- /dev/null +++ b/web/utils/object.ts @@ -0,0 +1,7 @@ +export function ObjectFromEntries>(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>(obj: T): (keyof T)[] { + return Object.keys(obj) as (keyof T)[] +}