このコミットが含まれているのは:
+36
-6
@@ -2,12 +2,36 @@ import { createInstance } from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import ainuNavigation from '@/locales/ain-Kana/navigation.json'
|
||||
import ainuCommon from '@/locales/ain-Kana/common.json'
|
||||
import ainuTags from '@/locales/ain-Kana/tags.json'
|
||||
import nipponeseNavigation from '@/locales/ja/navigation.json'
|
||||
import nipponeseCommon from '@/locales/ja/common.json'
|
||||
import nipponeseTags from '@/locales/ja/tags.json'
|
||||
|
||||
import type { LocaleCode } from '@/lib/locales'
|
||||
|
||||
export const i18n = createInstance ()
|
||||
|
||||
|
||||
// Keep Japanese placeholders editable without reporting them as completed translations.
|
||||
const translatedResource = (resource: Record<string, unknown>): Record<string, unknown> => {
|
||||
const untranslated = new Set (
|
||||
Array.isArray (resource._untranslated) ? resource._untranslated : [])
|
||||
const select = (entries: Record<string, unknown>, prefix = ''): Record<string, unknown> =>
|
||||
Object.fromEntries (Object.entries (entries).flatMap (([key, value]) => {
|
||||
const path = prefix ? `${ prefix }.${ key }` : key
|
||||
if (key === '_untranslated' || untranslated.has (path))
|
||||
return []
|
||||
const translated =
|
||||
(value != null && typeof value === 'object' && !(Array.isArray (value)))
|
||||
? select (value as Record<string, unknown>, path)
|
||||
: value
|
||||
return [[key, translated]]
|
||||
}))
|
||||
return select (resource)
|
||||
}
|
||||
|
||||
|
||||
void i18n.use (initReactI18next).init ({
|
||||
lng: 'ja',
|
||||
fallbackLng: 'ja',
|
||||
@@ -15,10 +39,13 @@ void i18n.use (initReactI18next).init ({
|
||||
load: 'currentOnly',
|
||||
initAsync: false,
|
||||
defaultNS: 'navigation',
|
||||
ns: ['navigation'],
|
||||
ns: ['navigation', 'common', 'tags'],
|
||||
resources: {
|
||||
ja: { navigation: nipponeseNavigation },
|
||||
'ain-Kana': { navigation: ainuNavigation } },
|
||||
ja: { navigation: nipponeseNavigation, common: nipponeseCommon, tags: nipponeseTags },
|
||||
'ain-Kana': {
|
||||
navigation: ainuNavigation,
|
||||
common: translatedResource (ainuCommon),
|
||||
tags: translatedResource (ainuTags) } },
|
||||
interpolation: { escapeValue: false },
|
||||
returnNull: false,
|
||||
returnEmptyString: false,
|
||||
@@ -50,7 +77,10 @@ export const untranslatedKeys = (
|
||||
|
||||
if (import.meta.env.DEV)
|
||||
{
|
||||
const missing = untranslatedKeys ('ain-Kana')
|
||||
if (missing.length > 0)
|
||||
console.warn ('[i18n] ain-Kana: untranslated navigation keys', missing)
|
||||
for (const namespace of ['navigation', 'common', 'tags'])
|
||||
{
|
||||
const missing = untranslatedKeys ('ain-Kana', namespace)
|
||||
if (missing.length > 0)
|
||||
console.warn (`[i18n] ain-Kana: untranslated ${ namespace } keys`, missing)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
|
||||
|
||||
export const useUITranslation = (namespace = 'common') => {
|
||||
const locale = useLocale ()
|
||||
const { t } = 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 { message }
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import type { ClassValue } from 'clsx'
|
||||
|
||||
import type { LocaleCode } from '@/lib/locales'
|
||||
|
||||
|
||||
export const toDate = (d: string | Date): Date => typeof d === 'string' ? new Date (d) : d
|
||||
|
||||
@@ -25,6 +27,24 @@ export const dateString = (
|
||||
second: unknown == null ? 'numeric' : undefined })
|
||||
|
||||
|
||||
export const localisedDateString = (value: string | Date, locale: LocaleCode): string => {
|
||||
if (locale === 'ja')
|
||||
return dateString (value)
|
||||
|
||||
const date = toDate (value)
|
||||
if (Number.isNaN (date.getTime ()))
|
||||
return dateString (value)
|
||||
|
||||
const parts = new Intl.DateTimeFormat ('en-CA', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hourCycle: 'h23' }).formatToParts (date)
|
||||
const part = (type: string) => parts.find (entry => entry.type === type)?.value ?? ''
|
||||
return `${ part ('year') }-${ part ('month') }-${ part ('day') }\u3000${
|
||||
part ('hour') }:${ part ('minute') }:${ part ('second') }`
|
||||
}
|
||||
|
||||
|
||||
export const originalCreatedAtString = (
|
||||
f: string | Date | null,
|
||||
b: string | Date | null): string => {
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする