このコミットが含まれているのは:
2026-10-18 02:22:00 +09:00
コミット 753a150128
14個のファイルの変更、189行の追加、73行の削除
+7 -6
ファイルの表示
@@ -1,4 +1,7 @@
import { useTranslation } from 'react-i18next'
import nikumaru from '@/assets/fonts/nikumaru.otf'
import { translateUIMessage } from '@/lib/i18n'
import type { FC } from 'react'
@@ -8,8 +11,8 @@ const COLUMN_COUNT = 12
const DevModeWatermark: FC = () => {
const { t } = useTranslation ('shared')
const text = t ('labels.development', { returnDetails: true })
const { i18n } = useTranslation ('shared')
const text = translateUIMessage ('labels.development', 'shared', i18n.language)
return (
<div
aria-hidden="true"
@@ -31,14 +34,14 @@ const DevModeWatermark: FC = () => {
{Array.from ({ length: COLUMN_COUNT }, (_, columnIndex) => (
<span
key={columnIndex}
lang={text.usedLng}
lang={text.language}
className={
'whitespace-nowrap text-3xl font-bold '
+ 'tracking-[0.3em] text-neutral-950/5 '
+ 'dark:text-white/10'
}
style={{ fontFamily: 'Nikumaru' }}>
{text.res}
{text.text}
</span>))}
</div>))}
</div>
@@ -47,5 +50,3 @@ const DevModeWatermark: FC = () => {
export default DevModeWatermark
import { useTranslation } from 'react-i18next'
+46
ファイルの表示
@@ -1,6 +1,8 @@
import { createInstance } from 'i18next'
import { initReactI18next } from 'react-i18next'
import type { TOptions } from 'i18next'
import type { LocaleCode } from '@/lib/locales'
export const i18n = createInstance ()
@@ -59,6 +61,50 @@ void i18n.use (initReactI18next).init ({
react: { useSuspense: false } })
export const translateUIMessage = (
key: string,
namespace: string,
locale: string,
values: Record<string, string | number> = { },
): { text: string; language: string } => {
const languages = new Set<string> ()
const visiting = new Set<string> ()
const collectLanguages = (reference: string, options: TOptions): string => {
const details = i18n.t (reference, { ...options,
returnDetails: true, returnObjects: false, nest: false, skipInterpolation: true })
const identity = `${details.usedLng}:${details.usedNS}:${details.exactUsedKey}`
if (visiting.has (identity) || typeof details.res !== 'string')
return details.usedLng
visiting.add (identity)
const nestingOptions = { ...i18n.options.interpolation, ...options,
ns: details.usedNS, lng: details.usedLng }
const remainder = i18n.services.interpolator.nest (details.res,
(nestedKey: string, nestedOptions: TOptions) => {
collectLanguages (nestedKey, nestedOptions)
return ' '
}, nestingOptions)
// References, punctuation and supplied values do not define the wrapper's language.
if (/\p{L}/u.test (remainder.replace (/\{\{[^}]*\}\}/g, '')))
languages.add (details.usedLng)
visiting.delete (identity)
return details.usedLng
}
const options = { ...values, ns: namespace, lng: locale }
const resolvedLanguage = collectLanguages (key, options)
// A single lang attribute cannot describe mixed fallback text: use the full Japanese message.
const mixed = languages.size > 1
const language = mixed ? 'ja' : languages.values ().next ().value ?? resolvedLanguage
const text = i18n.t (key, { ...options, lng: mixed ? 'ja' : resolvedLanguage,
returnDetails: false, returnObjects: false })
if (typeof text !== 'string')
{
console.warn ('[i18n] UI message must be a string', { key, namespace, locale })
return { text: key, language }
}
return { text, language }
}
export const untranslatedKeys = (
locale: LocaleCode,
namespace = 'navigation',
+3 -3
ファイルの表示
@@ -1,4 +1,4 @@
import { i18n } from '@/lib/i18n'
import { translateUIMessage } from '@/lib/i18n'
import type navigation from '@/locales/ja/navigation.json'
import type { LocaleCode } from '@/lib/locales'
@@ -10,10 +10,10 @@ export const uiMessage = (
locale: LocaleCode,
key: UIMessageKey,
values: Record<string, string | number> = { },
): string => i18n.t (key, { ...values, lng: locale, ns: 'navigation' })
): string => translateUIMessage (key, 'navigation', locale, values).text
export const uiMessageLanguage = (locale: LocaleCode, key: UIMessageKey): 'ja' | 'ain' =>
i18n.t (key, { lng: locale, ns: 'navigation', returnDetails: true }).usedLng === 'ain-Kana'
translateUIMessage (key, 'navigation', locale).language === 'ain-Kana'
? 'ain'
: 'ja'
+3 -3
ファイルの表示
@@ -2,16 +2,16 @@ import { createElement } from 'react'
import { useTranslation } from 'react-i18next'
import LocalisedText from '@/components/LocalisedText'
import { translateUIMessage } from '@/lib/i18n'
import { useLocale } from '@/lib/useLocale'
export const useUITranslation = (namespace = 'common') => {
const locale = useLocale ()
const { t } = useTranslation (namespace, { lng: locale })
useTranslation (namespace, { lng: locale })
const message = (key: string, values?: Record<string, string | number>) => {
const result = t (key, { ...values, returnDetails: true })
return { text: result.res, language: result.usedLng }
return translateUIMessage (key, namespace, locale, values)
}
const label = (key: string, values?: Record<string, string | number>) => {