このコミットが含まれているのは:
@@ -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'
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>) => {
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする