Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ When tests are configured:
The project supports multiple languages. Currently supported languages:

- 中文(简体)(Chinese Simplified)
- 中文(繁體)(Chinese Traditional)
- English (US)
- Deutsch (DE)
- Español (ES)
Expand Down
2 changes: 2 additions & 0 deletions components/datetime-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
tr,
vi,
zhCN,
zhTW,
type Locale as DateFnsLocale,
} from "date-fns/locale"
import { RiCalendarLine, RiCloseLine, RiTimeLine } from "@remixicon/react"
Expand Down Expand Up @@ -50,6 +51,7 @@ const DATE_FNS_LOCALES: Record<string, DateFnsLocale> = {
tr,
vi,
"zh-CN": zhCN,
"zh-TW": zhTW,
}

interface DateTimePickerProps extends Omit<React.ComponentProps<typeof Button>, "value" | "onChange"> {
Expand Down
7 changes: 4 additions & 3 deletions components/language-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next"
import { RiTranslate2 } from "@remixicon/react"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { changeLocale, LOCALE_CODES, type Locale } from "@/lib/i18n"
import { changeLocale, LOCALE_CODES, normalizeLocale } from "@/lib/i18n"

const languageConfig: Record<string, { text: string; icon: typeof RiTranslate2 }> = {
en: { text: "English", icon: RiTranslate2 },
ar: { text: "العربية", icon: RiTranslate2 },
zh: { text: "中文", icon: RiTranslate2 },
zh: { text: "簡體中文", icon: RiTranslate2 },
"zh-TW": { text: "繁體中文", icon: RiTranslate2 },
fr: { text: "Français", icon: RiTranslate2 },
tr: { text: "Türkçe", icon: RiTranslate2 },
ja: { text: "日本語", icon: RiTranslate2 },
Expand All @@ -30,7 +31,7 @@ const options = LOCALE_CODES.map((key) => ({

export function LanguageSwitcher() {
const { i18n, t } = useTranslation()
const currentLocale = (i18n.language?.split("-")[0] ?? "en") as Locale
const currentLocale = normalizeLocale(i18n.language)
const currentLanguage = languageConfig[currentLocale] ?? languageConfig.en

const CurrentIcon = currentLanguage.icon
Expand Down
2,317 changes: 2,317 additions & 0 deletions i18n/locales/zh-TW.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/datetime-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const DATE_FNS_LOCALE_CODE_MAP = {
tr: "tr",
vi: "vi",
zh: "zh-CN",
"zh-TW": "zh-TW",
}

export function getDateFnsLocaleCode(value) {
Expand Down
7 changes: 7 additions & 0 deletions lib/i18n-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const LOCALE_FILE_MAP = {
en: "en-US",
ar: "ar-MA",
zh: "zh-CN",
"zh-TW": "zh-TW",
ja: "ja-JP",
ko: "ko-KR",
de: "de-DE",
Expand Down Expand Up @@ -50,6 +51,12 @@ const LOCALE_ALIASES = {
zh: "zh",
zhcn: "zh",
zhhans: "zh",
zhhant: "zh-TW",
zhhanthk: "zh-TW",
zhhanttw: "zh-TW",
zhhk: "zh-TW",
zhmo: "zh-TW",
zhtw: "zh-TW",
}

function isLocale(value) {
Expand Down
7 changes: 7 additions & 0 deletions lib/i18n-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const LOCALE_FILE_MAP = {
en: "en-US",
ar: "ar-MA",
zh: "zh-CN",
"zh-TW": "zh-TW",
ja: "ja-JP",
ko: "ko-KR",
de: "de-DE",
Expand Down Expand Up @@ -52,6 +53,12 @@ const LOCALE_ALIASES: Record<string, Locale> = {
zh: "zh",
zhcn: "zh",
zhhans: "zh",
zhhant: "zh-TW",
zhhanthk: "zh-TW",
zhhanttw: "zh-TW",
zhhk: "zh-TW",
zhmo: "zh-TW",
zhtw: "zh-TW",
}

function isLocale(value: string): value is Locale {
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function initI18n() {
fallbackLng: DEFAULT_LOCALE,
supportedLngs: LOCALE_CODES,
nonExplicitSupportedLngs: true,
load: "languageOnly",
load: "currentOnly",
interpolation: {
escapeValue: false,
prefix: "{",
Expand Down
1 change: 1 addition & 0 deletions tests/lib/datetime-picker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
test("maps app locales to date-fns locale module codes", () => {
assert.equal(getDateFnsLocaleCode("zh-CN"), "zh-CN")
assert.equal(getDateFnsLocaleCode("zh"), "zh-CN")
assert.equal(getDateFnsLocaleCode("zh-TW"), "zh-TW")
assert.equal(getDateFnsLocaleCode("pt-BR"), "pt-BR")
assert.equal(getDateFnsLocaleCode("en-US"), "en-US")
assert.equal(getDateFnsLocaleCode("unknown"), "en-US")
Expand Down
16 changes: 16 additions & 0 deletions tests/lib/i18n-config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,25 @@ test("normalizeLocale accepts supported, browser, and legacy locale values", ()
assert.equal(normalizeLocale("unknown"), "en")
})

test("normalizeLocale routes Traditional Chinese variants to zh-TW", () => {
assert.deepEqual(["zh-TW", "zh_TW", "zh-Hant", "zh-Hant-TW", "zh-HK", "zh-MO"].map(normalizeLocale), [
"zh-TW",
"zh-TW",
"zh-TW",
"zh-TW",
"zh-TW",
"zh-TW",
])
})

test("normalizeLocale keeps Simplified Chinese variants on zh", () => {
assert.deepEqual(["zh", "zh-CN", "zh-Hans", "zh-SG"].map(normalizeLocale), ["zh", "zh", "zh", "zh"])
})

test("getLocaleFile resolves normalized locales to bundled locale files", () => {
assert.equal(getLocaleFile("en-US"), "en-US")
assert.equal(getLocaleFile("zh_CN"), "zh-CN")
assert.equal(getLocaleFile("zh-TW"), "zh-TW")
assert.equal(getLocaleFile("pt-BR"), "pt-BR")
})

Expand Down