feat(i18n): standardize bilingual catalogs and tooling
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sourceLocale": "en",
|
||||
"locales": ["en", "zh-Hans"],
|
||||
"sourceLocale": "en-US",
|
||||
"locales": ["en-US", "zh-Hans"],
|
||||
"catalogPath": "src/locales/{locale}/messages",
|
||||
"catalogSources": [
|
||||
"@workspace/ui/locales/{locale}",
|
||||
@@ -10,9 +10,14 @@
|
||||
"@workspace/blocks/media/locales/{locale}",
|
||||
"@workspace/blocks/navigation/locales/{locale}",
|
||||
"@workspace/blocks/notifications/locales/{locale}",
|
||||
"@workspace/blocks/search/locales/{locale}",
|
||||
"@workspace/search/locales/{locale}",
|
||||
"@workspace/lexical/locales/{locale}"
|
||||
],
|
||||
"include": ["src", "../../packages/blocks/src", "../../packages/ui/src"],
|
||||
"include": [
|
||||
"src",
|
||||
"../../packages/blocks/src",
|
||||
"../../packages/search/src",
|
||||
"../../packages/ui/src"
|
||||
],
|
||||
"exclude": ["**/*.test.{ts,tsx}", "**/node_modules/**"]
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
I18nProvider as UiI18nProvider,
|
||||
type CalendarLocale,
|
||||
} from "@workspace/ui/components/i18n"
|
||||
import { calendarLocale as englishCalendarLocale } from "@workspace/ui/locales/en"
|
||||
import { calendarLocale as englishCalendarLocale } from "@workspace/ui/locales/en-US"
|
||||
import { calendarLocale as simplifiedChineseCalendarLocale } from "@workspace/ui/locales/zh-Hans"
|
||||
|
||||
import { appLocales, DEFAULT_APP_LOCALE, type AppLocale } from "./app-locales"
|
||||
@@ -27,7 +27,7 @@ import { AppI18nDevtool } from "./app-i18n-devtool"
|
||||
import { setAppLocale as persistAppLocale } from "@/server/app-locale.functions"
|
||||
|
||||
const appCalendarLocales: Partial<Record<AppLocale, CalendarLocale>> = {
|
||||
en: englishCalendarLocale,
|
||||
"en-US": englishCalendarLocale,
|
||||
"zh-Hans": simplifiedChineseCalendarLocale,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,16 +7,11 @@ import { AppLanguageSwitcher } from "./app-language-switcher"
|
||||
|
||||
vi.mock("./app-i18n-provider", () => ({
|
||||
useAppLocale: () => ({
|
||||
locale: "en",
|
||||
locale: "en-US",
|
||||
setLocale: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("country-flag-icons/react/1x1", () => ({
|
||||
CN: () => <svg />,
|
||||
US: () => <svg />,
|
||||
}))
|
||||
|
||||
beforeAll(() => {
|
||||
vi.stubGlobal(
|
||||
"ResizeObserver",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LocaleDefinition } from "@workspace/i18n"
|
||||
|
||||
export type AppLocale = "en" | "zh-Hans"
|
||||
export type AppLocale = "en-US" | "zh-Hans"
|
||||
export type AppLocaleCountry = "CN" | "US"
|
||||
|
||||
export interface AppLocaleDefinition extends LocaleDefinition {
|
||||
@@ -16,7 +16,7 @@ export const appLocales = [
|
||||
direction: "ltr",
|
||||
languageTag: "en-US",
|
||||
label: "English",
|
||||
locale: "en",
|
||||
locale: "en-US",
|
||||
},
|
||||
{
|
||||
country: "CN",
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: en\n"
|
||||
"Language: en-US\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
@@ -23,6 +23,21 @@ msgstr "Account password"
|
||||
msgid "blocks.appearance.active"
|
||||
msgstr "Active"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:16
|
||||
msgid "blocks.chats.actions.addEmoji"
|
||||
msgstr "Add emoji"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||
msgid "blocks.chats.actions.addFiles"
|
||||
msgstr "Add files"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:8
|
||||
msgid "blocks.chats.actions.addImage"
|
||||
msgstr "Add image"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:276
|
||||
msgid "app.navigation.system-members-admins"
|
||||
@@ -33,6 +48,11 @@ msgstr "Administrator accounts"
|
||||
msgid "app.navigation.after-sales-center"
|
||||
msgstr "After-sales"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:12
|
||||
msgid "blocks.chats.emojiPicker.allEmojis"
|
||||
msgstr "All emojis"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
|
||||
msgid "blocks.appearance.color.amber"
|
||||
@@ -73,6 +93,11 @@ msgstr "Awaiting payment"
|
||||
msgid "app.navigation.trade-orders-awaiting-shipment"
|
||||
msgstr "Awaiting shipment"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:20
|
||||
msgid "blocks.chats.actions.backToConversations"
|
||||
msgstr "Back to conversations"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
|
||||
msgid "blocks.appearance.baseColor"
|
||||
@@ -124,7 +149,7 @@ msgid "app.navigation.report-channels"
|
||||
msgstr "Channel analysis"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:112
|
||||
msgid "blocks.chats.title"
|
||||
msgstr "Chats"
|
||||
|
||||
@@ -133,6 +158,11 @@ msgstr "Chats"
|
||||
msgid "blocks.notifications.error.description"
|
||||
msgstr "Check your network connection and try again."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:108
|
||||
msgid "blocks.chats.selectConversation.description"
|
||||
msgstr "Choose a conversation from the list to read its messages."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:29
|
||||
msgid "ui.common.close"
|
||||
@@ -158,6 +188,11 @@ msgstr "Completed orders"
|
||||
msgid "app.navigation.content-assets"
|
||||
msgstr "Content"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:24
|
||||
msgid "blocks.chats.actions.conversationDetails"
|
||||
msgstr "Conversation details"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:192
|
||||
msgid "app.navigation.marketing-coupons"
|
||||
@@ -218,6 +253,11 @@ msgstr "Dark theme"
|
||||
msgid "app.navigation.data-and-finance"
|
||||
msgstr "Data and finance"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:52
|
||||
msgid "blocks.chats.messageStatus.delivered"
|
||||
msgstr "Delivered"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:296
|
||||
msgid "app.navigation.system-notification-logs"
|
||||
@@ -343,6 +383,11 @@ msgstr "Light theme"
|
||||
msgid "blocks.appearance.color.lime"
|
||||
msgstr "Lime"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:28
|
||||
msgid "blocks.chats.actions.loadEarlier"
|
||||
msgstr "Load earlier messages"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:37
|
||||
msgid "ui.common.loading"
|
||||
@@ -383,6 +428,11 @@ msgstr "Media"
|
||||
msgid "app.navigation.loyalty-center"
|
||||
msgstr "Membership benefits"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:84
|
||||
msgid "blocks.chats.emptyMessages.description"
|
||||
msgstr "Messages in this conversation will appear here."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
|
||||
msgid "blocks.appearance.color.mist"
|
||||
@@ -433,11 +483,31 @@ msgstr "Next"
|
||||
msgid "ui.carousel.nextSlide"
|
||||
msgstr "Next slide"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:80
|
||||
msgid "blocks.chats.emptyConversations.title"
|
||||
msgstr "No conversations"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:92
|
||||
msgid "blocks.chats.emptySearch"
|
||||
msgstr "No conversations match your search."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:88
|
||||
msgid "blocks.chats.emptyMessages.title"
|
||||
msgstr "No messages yet"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
|
||||
msgid "blocks.notifications.empty.title"
|
||||
msgstr "No notifications"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:56
|
||||
msgid "blocks.chats.messageStatus.failed"
|
||||
msgstr "Not sent"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:292
|
||||
msgid "app.navigation.system-notification-templates"
|
||||
@@ -543,6 +613,11 @@ msgstr "Pink"
|
||||
msgid "app.navigation.loyalty-points"
|
||||
msgstr "Points rules"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:44
|
||||
msgid "blocks.chats.actions.sendOnEnter"
|
||||
msgstr "Press Enter to send"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:65
|
||||
msgid "ui.pagination.previous"
|
||||
@@ -598,11 +673,21 @@ msgstr "Products and inventory"
|
||||
msgid "blocks.appearance.color.purple"
|
||||
msgstr "Purple"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:60
|
||||
msgid "blocks.chats.messageStatus.read"
|
||||
msgstr "Read"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:12
|
||||
msgid "app.navigation.overview-realtime"
|
||||
msgstr "Real-time monitoring"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:64
|
||||
msgid "blocks.chats.emojiPicker.recentEmojis"
|
||||
msgstr "Recently used"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:256
|
||||
msgid "app.navigation.finance-reconciliation"
|
||||
@@ -638,6 +723,11 @@ msgstr "Refresh the current page data"
|
||||
msgid "app.navigation.after-sales-refunds"
|
||||
msgstr "Refund review"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:32
|
||||
msgid "blocks.chats.actions.removeAttachment"
|
||||
msgstr "Remove {name}"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:228
|
||||
msgid "app.navigation.report-center"
|
||||
@@ -668,6 +758,36 @@ msgstr "Rose"
|
||||
msgid "app.navigation.report-sales"
|
||||
msgstr "Sales analysis"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:100
|
||||
msgid "blocks.chats.search"
|
||||
msgstr "Search conversations"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:104
|
||||
msgid "blocks.chats.selectConversation"
|
||||
msgstr "Select conversation {title}"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:40
|
||||
msgid "blocks.chats.actions.send"
|
||||
msgstr "Send"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:72
|
||||
msgid "blocks.chats.messageStatus.sending"
|
||||
msgstr "Sending"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:48
|
||||
msgid "blocks.chats.actions.sending"
|
||||
msgstr "Sending…"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:68
|
||||
msgid "blocks.chats.messageStatus.sent"
|
||||
msgstr "Sent"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:252
|
||||
msgid "app.navigation.finance-settlements"
|
||||
@@ -698,6 +818,11 @@ msgstr "Sky"
|
||||
msgid "app.navigation.catalog-product-specifications"
|
||||
msgstr "Specification templates"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:76
|
||||
msgid "blocks.chats.emptyConversations.description"
|
||||
msgstr "Start a conversation to see it here."
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:80
|
||||
msgid "app.navigation.inventory-transfers"
|
||||
@@ -803,6 +928,11 @@ msgstr "Toggle theme"
|
||||
msgid "app.navigation.after-sales-disputes"
|
||||
msgstr "Transaction disputes"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:36
|
||||
msgid "blocks.chats.actions.retry"
|
||||
msgstr "Try again"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
|
||||
msgid "blocks.notifications.retry"
|
||||
@@ -843,6 +973,11 @@ msgstr "Violet"
|
||||
msgid "app.navigation.system-integration-webhooks"
|
||||
msgstr "Webhook configuration"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:96
|
||||
msgid "blocks.chats.messagePlaceholder"
|
||||
msgstr "Write a message…"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
|
||||
msgid "blocks.appearance.color.yellow"
|
||||
File diff suppressed because one or more lines are too long
@@ -23,6 +23,21 @@ msgstr "账号密码"
|
||||
msgid "blocks.appearance.active"
|
||||
msgstr "使用中"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:16
|
||||
msgid "blocks.chats.actions.addEmoji"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||
msgid "blocks.chats.actions.addFiles"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:8
|
||||
msgid "blocks.chats.actions.addImage"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:276
|
||||
msgid "app.navigation.system-members-admins"
|
||||
@@ -33,6 +48,11 @@ msgstr "管理员账号"
|
||||
msgid "app.navigation.after-sales-center"
|
||||
msgstr "售后中心"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:12
|
||||
msgid "blocks.chats.emojiPicker.allEmojis"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
|
||||
msgid "blocks.appearance.color.amber"
|
||||
@@ -73,6 +93,11 @@ msgstr "待付款订单"
|
||||
msgid "app.navigation.trade-orders-awaiting-shipment"
|
||||
msgstr "待发货订单"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:20
|
||||
msgid "blocks.chats.actions.backToConversations"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
|
||||
msgid "blocks.appearance.baseColor"
|
||||
@@ -124,7 +149,7 @@ msgid "app.navigation.report-channels"
|
||||
msgstr "渠道分析"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:112
|
||||
msgid "blocks.chats.title"
|
||||
msgstr "聊天"
|
||||
|
||||
@@ -133,6 +158,11 @@ msgstr "聊天"
|
||||
msgid "blocks.notifications.error.description"
|
||||
msgstr "请检查网络连接后重试。"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:108
|
||||
msgid "blocks.chats.selectConversation.description"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:29
|
||||
msgid "ui.common.close"
|
||||
@@ -158,6 +188,11 @@ msgstr "已完成订单"
|
||||
msgid "app.navigation.content-assets"
|
||||
msgstr "内容素材"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:24
|
||||
msgid "blocks.chats.actions.conversationDetails"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:192
|
||||
msgid "app.navigation.marketing-coupons"
|
||||
@@ -218,6 +253,11 @@ msgstr "深色主题"
|
||||
msgid "app.navigation.data-and-finance"
|
||||
msgstr "数据与财务"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:52
|
||||
msgid "blocks.chats.messageStatus.delivered"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:296
|
||||
msgid "app.navigation.system-notification-logs"
|
||||
@@ -343,6 +383,11 @@ msgstr "浅色主题"
|
||||
msgid "blocks.appearance.color.lime"
|
||||
msgstr "青柠色"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:28
|
||||
msgid "blocks.chats.actions.loadEarlier"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:37
|
||||
msgid "ui.common.loading"
|
||||
@@ -383,6 +428,11 @@ msgstr "媒体资源"
|
||||
msgid "app.navigation.loyalty-center"
|
||||
msgstr "会员权益"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:84
|
||||
msgid "blocks.chats.emptyMessages.description"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
|
||||
msgid "blocks.appearance.color.mist"
|
||||
@@ -433,11 +483,31 @@ msgstr "下一页"
|
||||
msgid "ui.carousel.nextSlide"
|
||||
msgstr "下一张"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:80
|
||||
msgid "blocks.chats.emptyConversations.title"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:92
|
||||
msgid "blocks.chats.emptySearch"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:88
|
||||
msgid "blocks.chats.emptyMessages.title"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
|
||||
msgid "blocks.notifications.empty.title"
|
||||
msgstr "暂无通知"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:56
|
||||
msgid "blocks.chats.messageStatus.failed"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:292
|
||||
msgid "app.navigation.system-notification-templates"
|
||||
@@ -543,6 +613,11 @@ msgstr "粉色"
|
||||
msgid "app.navigation.loyalty-points"
|
||||
msgstr "积分规则"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:44
|
||||
msgid "blocks.chats.actions.sendOnEnter"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/ui/src/components/i18n.tsx:65
|
||||
msgid "ui.pagination.previous"
|
||||
@@ -598,11 +673,21 @@ msgstr "商品与库存"
|
||||
msgid "blocks.appearance.color.purple"
|
||||
msgstr "紫色"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:60
|
||||
msgid "blocks.chats.messageStatus.read"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:12
|
||||
msgid "app.navigation.overview-realtime"
|
||||
msgstr "实时监控"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:64
|
||||
msgid "blocks.chats.emojiPicker.recentEmojis"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:256
|
||||
msgid "app.navigation.finance-reconciliation"
|
||||
@@ -638,6 +723,11 @@ msgstr "刷新当前页面的数据"
|
||||
msgid "app.navigation.after-sales-refunds"
|
||||
msgstr "退款审核"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:32
|
||||
msgid "blocks.chats.actions.removeAttachment"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:228
|
||||
msgid "app.navigation.report-center"
|
||||
@@ -668,6 +758,36 @@ msgstr "玫红色"
|
||||
msgid "app.navigation.report-sales"
|
||||
msgstr "销售分析"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:100
|
||||
msgid "blocks.chats.search"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:104
|
||||
msgid "blocks.chats.selectConversation"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:40
|
||||
msgid "blocks.chats.actions.send"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:72
|
||||
msgid "blocks.chats.messageStatus.sending"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:48
|
||||
msgid "blocks.chats.actions.sending"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:68
|
||||
msgid "blocks.chats.messageStatus.sent"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:252
|
||||
msgid "app.navigation.finance-settlements"
|
||||
@@ -698,6 +818,11 @@ msgstr "天蓝色"
|
||||
msgid "app.navigation.catalog-product-specifications"
|
||||
msgstr "规格模板"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:76
|
||||
msgid "blocks.chats.emptyConversations.description"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: src/data/navigation-messages.ts:80
|
||||
msgid "app.navigation.inventory-transfers"
|
||||
@@ -803,6 +928,11 @@ msgstr "切换主题"
|
||||
msgid "app.navigation.after-sales-disputes"
|
||||
msgstr "交易纠纷"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:36
|
||||
msgid "blocks.chats.actions.retry"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
|
||||
msgid "blocks.notifications.retry"
|
||||
@@ -843,6 +973,11 @@ msgstr "紫罗兰色"
|
||||
msgid "app.navigation.system-integration-webhooks"
|
||||
msgstr "Webhook 配置"
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/chats/messages.ts:96
|
||||
msgid "blocks.chats.messagePlaceholder"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-explicit-id
|
||||
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
|
||||
msgid "blocks.appearance.color.yellow"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable-next-line import/no-unassigned-import -- Enforces server-only module usage.
|
||||
import "@tanstack/react-start/server-only"
|
||||
import { getCookie, setCookie } from "@tanstack/react-start/server"
|
||||
|
||||
|
||||
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { appearanceMessages, themeColorMessages } from "../messages"
|
||||
import { appearanceCatalogLocales } from "./catalogs"
|
||||
import { messages as de } from "./de"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as es } from "./es"
|
||||
import { messages as fr } from "./fr"
|
||||
import { messages as ja } from "./ja"
|
||||
import { messages as ko } from "./ko"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
import { messages as zhHant } from "./zh-Hant"
|
||||
|
||||
describe("appearance locale catalogs", () => {
|
||||
it("ship every appearance message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
ko,
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
"zh-Hant": zhHant,
|
||||
},
|
||||
locales: appearanceCatalogLocales,
|
||||
messageIds: [
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "Aktiv",
|
||||
"blocks.appearance.baseColor": "Basisfarbe",
|
||||
"blocks.appearance.color.amber": "Bernstein",
|
||||
"blocks.appearance.color.blue": "Blau",
|
||||
"blocks.appearance.color.cyan": "Cyan",
|
||||
"blocks.appearance.color.emerald": "Smaragd",
|
||||
"blocks.appearance.color.fuchsia": "Fuchsia",
|
||||
"blocks.appearance.color.green": "Grün",
|
||||
"blocks.appearance.color.indigo": "Indigo",
|
||||
"blocks.appearance.color.lime": "Limette",
|
||||
"blocks.appearance.color.mauve": "Malve",
|
||||
"blocks.appearance.color.mist": "Nebel",
|
||||
"blocks.appearance.color.neutral": "Neutral",
|
||||
"blocks.appearance.color.olive": "Oliv",
|
||||
"blocks.appearance.color.orange": "Orange",
|
||||
"blocks.appearance.color.pink": "Rosa",
|
||||
"blocks.appearance.color.purple": "Lila",
|
||||
"blocks.appearance.color.red": "Rot",
|
||||
"blocks.appearance.color.rose": "Rose",
|
||||
"blocks.appearance.color.sky": "Himmelblau",
|
||||
"blocks.appearance.color.stone": "Stein",
|
||||
"blocks.appearance.color.taupe": "Taupe",
|
||||
"blocks.appearance.color.teal": "Blaugrün",
|
||||
"blocks.appearance.color.violet": "Violett",
|
||||
"blocks.appearance.color.yellow": "Gelb",
|
||||
"blocks.appearance.color.zinc": "Zink",
|
||||
"blocks.appearance.command.description":
|
||||
"Design- und Layout-Einstellungen öffnen",
|
||||
"blocks.appearance.compact.description":
|
||||
"Eine kompakte Breite für den Hauptinhalt verwenden",
|
||||
"blocks.appearance.compact.title": "Kompaktes Layout",
|
||||
"blocks.appearance.description": "Design- und Layout-Einstellungen",
|
||||
"blocks.appearance.mode.dark": "Dunkel",
|
||||
"blocks.appearance.mode.dark.description":
|
||||
"Die Oberfläche verwendet immer das dunkle Design",
|
||||
"blocks.appearance.mode.light": "Hell",
|
||||
"blocks.appearance.mode.light.description":
|
||||
"Die Oberfläche verwendet immer das helle Design",
|
||||
"blocks.appearance.mode.system": "System",
|
||||
"blocks.appearance.mode.system.description":
|
||||
"Das Design der Oberfläche folgt der Systemeinstellung",
|
||||
"blocks.appearance.mode.title": "Darstellungsmodus",
|
||||
"blocks.appearance.primaryColor": "Primärfarbe",
|
||||
"blocks.appearance.theme.dark": "Dunkles Design",
|
||||
"blocks.appearance.theme.dark.description":
|
||||
"Dieses Design wird im dunklen Systemmodus verwendet",
|
||||
"blocks.appearance.theme.light": "Helles Design",
|
||||
"blocks.appearance.theme.light.description":
|
||||
"Dieses Design wird im hellen Systemmodus verwendet",
|
||||
"blocks.appearance.title": "Darstellung",
|
||||
"blocks.appearance.toggleTheme": "Design wechseln",
|
||||
"blocks.appearance.toggleTheme.description":
|
||||
"Zwischen hellem und dunklem Design wechseln",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
+1
-1
@@ -3,7 +3,7 @@ import type { MessageDescriptor } from "@workspace/i18n"
|
||||
import { appearanceMessages, themeColorMessages } from "../messages.ts"
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
|
||||
function createSourceCatalog(
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "Activo",
|
||||
"blocks.appearance.baseColor": "Color base",
|
||||
"blocks.appearance.color.amber": "Ámbar",
|
||||
"blocks.appearance.color.blue": "Azul",
|
||||
"blocks.appearance.color.cyan": "Cian",
|
||||
"blocks.appearance.color.emerald": "Esmeralda",
|
||||
"blocks.appearance.color.fuchsia": "Fucsia",
|
||||
"blocks.appearance.color.green": "Verde",
|
||||
"blocks.appearance.color.indigo": "Índigo",
|
||||
"blocks.appearance.color.lime": "Lima",
|
||||
"blocks.appearance.color.mauve": "Malva",
|
||||
"blocks.appearance.color.mist": "Niebla",
|
||||
"blocks.appearance.color.neutral": "Neutro",
|
||||
"blocks.appearance.color.olive": "Oliva",
|
||||
"blocks.appearance.color.orange": "Naranja",
|
||||
"blocks.appearance.color.pink": "Rosa",
|
||||
"blocks.appearance.color.purple": "Morado",
|
||||
"blocks.appearance.color.red": "Rojo",
|
||||
"blocks.appearance.color.rose": "Rosa intenso",
|
||||
"blocks.appearance.color.sky": "Celeste",
|
||||
"blocks.appearance.color.stone": "Piedra",
|
||||
"blocks.appearance.color.taupe": "Topo",
|
||||
"blocks.appearance.color.teal": "Verde azulado",
|
||||
"blocks.appearance.color.violet": "Violeta",
|
||||
"blocks.appearance.color.yellow": "Amarillo",
|
||||
"blocks.appearance.color.zinc": "Zinc",
|
||||
"blocks.appearance.command.description":
|
||||
"Abrir las preferencias de tema y diseño",
|
||||
"blocks.appearance.compact.description":
|
||||
"Usar un ancho compacto para el contenido principal",
|
||||
"blocks.appearance.compact.title": "Diseño compacto",
|
||||
"blocks.appearance.description": "Preferencias de tema y diseño",
|
||||
"blocks.appearance.mode.dark": "Oscuro",
|
||||
"blocks.appearance.mode.dark.description":
|
||||
"La interfaz siempre usará el tema oscuro",
|
||||
"blocks.appearance.mode.light": "Claro",
|
||||
"blocks.appearance.mode.light.description":
|
||||
"La interfaz siempre usará el tema claro",
|
||||
"blocks.appearance.mode.system": "Sistema",
|
||||
"blocks.appearance.mode.system.description":
|
||||
"El tema de la interfaz seguirá la apariencia del sistema",
|
||||
"blocks.appearance.mode.title": "Modo de tema",
|
||||
"blocks.appearance.primaryColor": "Color principal",
|
||||
"blocks.appearance.theme.dark": "Tema oscuro",
|
||||
"blocks.appearance.theme.dark.description":
|
||||
"Este tema se usa cuando el sistema está en modo oscuro",
|
||||
"blocks.appearance.theme.light": "Tema claro",
|
||||
"blocks.appearance.theme.light.description":
|
||||
"Este tema se usa cuando el sistema está en modo claro",
|
||||
"blocks.appearance.title": "Apariencia",
|
||||
"blocks.appearance.toggleTheme": "Cambiar tema",
|
||||
"blocks.appearance.toggleTheme.description":
|
||||
"Cambiar entre los temas claro y oscuro",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "Actif",
|
||||
"blocks.appearance.baseColor": "Couleur de base",
|
||||
"blocks.appearance.color.amber": "Ambre",
|
||||
"blocks.appearance.color.blue": "Bleu",
|
||||
"blocks.appearance.color.cyan": "Cyan",
|
||||
"blocks.appearance.color.emerald": "Émeraude",
|
||||
"blocks.appearance.color.fuchsia": "Fuchsia",
|
||||
"blocks.appearance.color.green": "Vert",
|
||||
"blocks.appearance.color.indigo": "Indigo",
|
||||
"blocks.appearance.color.lime": "Citron vert",
|
||||
"blocks.appearance.color.mauve": "Mauve",
|
||||
"blocks.appearance.color.mist": "Brume",
|
||||
"blocks.appearance.color.neutral": "Neutre",
|
||||
"blocks.appearance.color.olive": "Olive",
|
||||
"blocks.appearance.color.orange": "Orange",
|
||||
"blocks.appearance.color.pink": "Rose",
|
||||
"blocks.appearance.color.purple": "Pourpre",
|
||||
"blocks.appearance.color.red": "Rouge",
|
||||
"blocks.appearance.color.rose": "Rose soutenu",
|
||||
"blocks.appearance.color.sky": "Bleu ciel",
|
||||
"blocks.appearance.color.stone": "Pierre",
|
||||
"blocks.appearance.color.taupe": "Taupe",
|
||||
"blocks.appearance.color.teal": "Sarcelle",
|
||||
"blocks.appearance.color.violet": "Violet",
|
||||
"blocks.appearance.color.yellow": "Jaune",
|
||||
"blocks.appearance.color.zinc": "Zinc",
|
||||
"blocks.appearance.command.description":
|
||||
"Ouvrir les préférences de thème et de mise en page",
|
||||
"blocks.appearance.compact.description":
|
||||
"Utiliser une largeur compacte pour le contenu principal",
|
||||
"blocks.appearance.compact.title": "Mise en page compacte",
|
||||
"blocks.appearance.description": "Préférences de thème et de mise en page",
|
||||
"blocks.appearance.mode.dark": "Sombre",
|
||||
"blocks.appearance.mode.dark.description":
|
||||
"L’interface utilisera toujours le thème sombre",
|
||||
"blocks.appearance.mode.light": "Clair",
|
||||
"blocks.appearance.mode.light.description":
|
||||
"L’interface utilisera toujours le thème clair",
|
||||
"blocks.appearance.mode.system": "Système",
|
||||
"blocks.appearance.mode.system.description":
|
||||
"Le thème de l’interface suivra l’apparence du système",
|
||||
"blocks.appearance.mode.title": "Mode du thème",
|
||||
"blocks.appearance.primaryColor": "Couleur principale",
|
||||
"blocks.appearance.theme.dark": "Thème sombre",
|
||||
"blocks.appearance.theme.dark.description":
|
||||
"Ce thème est utilisé lorsque le système est en mode sombre",
|
||||
"blocks.appearance.theme.light": "Thème clair",
|
||||
"blocks.appearance.theme.light.description":
|
||||
"Ce thème est utilisé lorsque le système est en mode clair",
|
||||
"blocks.appearance.title": "Apparence",
|
||||
"blocks.appearance.toggleTheme": "Changer de thème",
|
||||
"blocks.appearance.toggleTheme.description":
|
||||
"Basculer entre les thèmes clair et sombre",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "使用中",
|
||||
"blocks.appearance.baseColor": "ベースカラー",
|
||||
"blocks.appearance.color.amber": "アンバー",
|
||||
"blocks.appearance.color.blue": "ブルー",
|
||||
"blocks.appearance.color.cyan": "シアン",
|
||||
"blocks.appearance.color.emerald": "エメラルド",
|
||||
"blocks.appearance.color.fuchsia": "フクシア",
|
||||
"blocks.appearance.color.green": "グリーン",
|
||||
"blocks.appearance.color.indigo": "インディゴ",
|
||||
"blocks.appearance.color.lime": "ライム",
|
||||
"blocks.appearance.color.mauve": "モーブ",
|
||||
"blocks.appearance.color.mist": "ミスト",
|
||||
"blocks.appearance.color.neutral": "ニュートラル",
|
||||
"blocks.appearance.color.olive": "オリーブ",
|
||||
"blocks.appearance.color.orange": "オレンジ",
|
||||
"blocks.appearance.color.pink": "ピンク",
|
||||
"blocks.appearance.color.purple": "パープル",
|
||||
"blocks.appearance.color.red": "レッド",
|
||||
"blocks.appearance.color.rose": "ローズ",
|
||||
"blocks.appearance.color.sky": "スカイ",
|
||||
"blocks.appearance.color.stone": "ストーン",
|
||||
"blocks.appearance.color.taupe": "トープ",
|
||||
"blocks.appearance.color.teal": "ティール",
|
||||
"blocks.appearance.color.violet": "バイオレット",
|
||||
"blocks.appearance.color.yellow": "イエロー",
|
||||
"blocks.appearance.color.zinc": "ジンク",
|
||||
"blocks.appearance.command.description": "テーマとレイアウト設定を開く",
|
||||
"blocks.appearance.compact.description":
|
||||
"メインページのコンテンツをコンパクトな幅で表示します",
|
||||
"blocks.appearance.compact.title": "コンパクトレイアウト",
|
||||
"blocks.appearance.description": "テーマとレイアウトの設定",
|
||||
"blocks.appearance.mode.dark": "ダーク",
|
||||
"blocks.appearance.mode.dark.description": "常にダークテーマを使用します",
|
||||
"blocks.appearance.mode.light": "ライト",
|
||||
"blocks.appearance.mode.light.description": "常にライトテーマを使用します",
|
||||
"blocks.appearance.mode.system": "システム",
|
||||
"blocks.appearance.mode.system.description":
|
||||
"インターフェースのテーマをシステムの外観に合わせます",
|
||||
"blocks.appearance.mode.title": "テーマモード",
|
||||
"blocks.appearance.primaryColor": "プライマリカラー",
|
||||
"blocks.appearance.theme.dark": "ダークテーマ",
|
||||
"blocks.appearance.theme.dark.description":
|
||||
"システムがダークモードのときに使用するテーマです",
|
||||
"blocks.appearance.theme.light": "ライトテーマ",
|
||||
"blocks.appearance.theme.light.description":
|
||||
"システムがライトモードのときに使用するテーマです",
|
||||
"blocks.appearance.title": "外観",
|
||||
"blocks.appearance.toggleTheme": "テーマを切り替える",
|
||||
"blocks.appearance.toggleTheme.description":
|
||||
"ライトテーマとダークテーマを切り替えます",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "사용 중",
|
||||
"blocks.appearance.baseColor": "기본 색상",
|
||||
"blocks.appearance.color.amber": "호박색",
|
||||
"blocks.appearance.color.blue": "파란색",
|
||||
"blocks.appearance.color.cyan": "청록색",
|
||||
"blocks.appearance.color.emerald": "에메랄드",
|
||||
"blocks.appearance.color.fuchsia": "자홍색",
|
||||
"blocks.appearance.color.green": "초록색",
|
||||
"blocks.appearance.color.indigo": "남색",
|
||||
"blocks.appearance.color.lime": "라임",
|
||||
"blocks.appearance.color.mauve": "연보라",
|
||||
"blocks.appearance.color.mist": "안개색",
|
||||
"blocks.appearance.color.neutral": "중성색",
|
||||
"blocks.appearance.color.olive": "올리브",
|
||||
"blocks.appearance.color.orange": "주황색",
|
||||
"blocks.appearance.color.pink": "분홍색",
|
||||
"blocks.appearance.color.purple": "보라색",
|
||||
"blocks.appearance.color.red": "빨간색",
|
||||
"blocks.appearance.color.rose": "장미색",
|
||||
"blocks.appearance.color.sky": "하늘색",
|
||||
"blocks.appearance.color.stone": "돌색",
|
||||
"blocks.appearance.color.taupe": "회갈색",
|
||||
"blocks.appearance.color.teal": "틸",
|
||||
"blocks.appearance.color.violet": "제비꽃색",
|
||||
"blocks.appearance.color.yellow": "노란색",
|
||||
"blocks.appearance.color.zinc": "아연색",
|
||||
"blocks.appearance.command.description": "테마 및 레이아웃 환경설정 열기",
|
||||
"blocks.appearance.compact.description":
|
||||
"기본 페이지 콘텐츠에 좁은 너비를 사용합니다",
|
||||
"blocks.appearance.compact.title": "컴팩트 레이아웃",
|
||||
"blocks.appearance.description": "테마 및 레이아웃 환경설정",
|
||||
"blocks.appearance.mode.dark": "어둡게",
|
||||
"blocks.appearance.mode.dark.description":
|
||||
"인터페이스에서 항상 어두운 테마를 사용합니다",
|
||||
"blocks.appearance.mode.light": "밝게",
|
||||
"blocks.appearance.mode.light.description":
|
||||
"인터페이스에서 항상 밝은 테마를 사용합니다",
|
||||
"blocks.appearance.mode.system": "시스템",
|
||||
"blocks.appearance.mode.system.description":
|
||||
"인터페이스 테마가 시스템 화면 모드를 따릅니다",
|
||||
"blocks.appearance.mode.title": "테마 모드",
|
||||
"blocks.appearance.primaryColor": "주요 색상",
|
||||
"blocks.appearance.theme.dark": "어두운 테마",
|
||||
"blocks.appearance.theme.dark.description":
|
||||
"시스템이 어두운 모드일 때 사용하는 테마입니다",
|
||||
"blocks.appearance.theme.light": "밝은 테마",
|
||||
"blocks.appearance.theme.light.description":
|
||||
"시스템이 밝은 모드일 때 사용하는 테마입니다",
|
||||
"blocks.appearance.title": "화면 모양",
|
||||
"blocks.appearance.toggleTheme": "테마 전환",
|
||||
"blocks.appearance.toggleTheme.description":
|
||||
"밝은 테마와 어두운 테마 사이를 전환합니다",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
|
||||
export const messages = {
|
||||
"blocks.appearance.active": "使用中",
|
||||
"blocks.appearance.baseColor": "基礎顏色",
|
||||
"blocks.appearance.color.amber": "琥珀色",
|
||||
"blocks.appearance.color.blue": "藍色",
|
||||
"blocks.appearance.color.cyan": "青色",
|
||||
"blocks.appearance.color.emerald": "翠綠色",
|
||||
"blocks.appearance.color.fuchsia": "紫紅色",
|
||||
"blocks.appearance.color.green": "綠色",
|
||||
"blocks.appearance.color.indigo": "靛藍色",
|
||||
"blocks.appearance.color.lime": "萊姆色",
|
||||
"blocks.appearance.color.mauve": "淡紫色",
|
||||
"blocks.appearance.color.mist": "霧灰色",
|
||||
"blocks.appearance.color.neutral": "中性灰",
|
||||
"blocks.appearance.color.olive": "橄欖色",
|
||||
"blocks.appearance.color.orange": "橙色",
|
||||
"blocks.appearance.color.pink": "粉紅色",
|
||||
"blocks.appearance.color.purple": "紫色",
|
||||
"blocks.appearance.color.red": "紅色",
|
||||
"blocks.appearance.color.rose": "玫瑰色",
|
||||
"blocks.appearance.color.sky": "天藍色",
|
||||
"blocks.appearance.color.stone": "石灰色",
|
||||
"blocks.appearance.color.taupe": "灰褐色",
|
||||
"blocks.appearance.color.teal": "藍綠色",
|
||||
"blocks.appearance.color.violet": "紫羅蘭色",
|
||||
"blocks.appearance.color.yellow": "黃色",
|
||||
"blocks.appearance.color.zinc": "鋅灰色",
|
||||
"blocks.appearance.command.description": "開啟主題與版面配置偏好",
|
||||
"blocks.appearance.compact.description": "使用較緊湊的主頁內容寬度",
|
||||
"blocks.appearance.compact.title": "緊湊版面配置",
|
||||
"blocks.appearance.description": "主題與版面配置偏好",
|
||||
"blocks.appearance.mode.dark": "深色",
|
||||
"blocks.appearance.mode.dark.description": "介面將一律使用深色主題",
|
||||
"blocks.appearance.mode.light": "淺色",
|
||||
"blocks.appearance.mode.light.description": "介面將一律使用淺色主題",
|
||||
"blocks.appearance.mode.system": "系統",
|
||||
"blocks.appearance.mode.system.description": "介面主題將跟隨系統外觀設定",
|
||||
"blocks.appearance.mode.title": "主題模式",
|
||||
"blocks.appearance.primaryColor": "主要顏色",
|
||||
"blocks.appearance.theme.dark": "深色主題",
|
||||
"blocks.appearance.theme.dark.description": "系統使用深色模式時套用此主題",
|
||||
"blocks.appearance.theme.light": "淺色主題",
|
||||
"blocks.appearance.theme.light.description": "系統使用淺色模式時套用此主題",
|
||||
"blocks.appearance.title": "介面外觀",
|
||||
"blocks.appearance.toggleTheme": "切換主題",
|
||||
"blocks.appearance.toggleTheme.description": "在淺色與深色主題之間切換",
|
||||
} as const satisfies AppearanceMessageCatalog
|
||||
@@ -5,7 +5,7 @@ import { I18nProvider } from "@workspace/i18n"
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { ChatMessageList } from "./chat-message-list"
|
||||
import { messages as englishMessages } from "./locales/en"
|
||||
import { messages as englishMessages } from "./locales/en-US"
|
||||
import type { ChatConversation } from "./types"
|
||||
|
||||
const conversation: ChatConversation = {
|
||||
@@ -57,7 +57,7 @@ describe("ChatMessageList", () => {
|
||||
)
|
||||
|
||||
render(
|
||||
<I18nProvider locale="en" catalogs={{ en: englishMessages }}>
|
||||
<I18nProvider locale="en-US" catalogs={{ "en-US": englishMessages }}>
|
||||
<ChatMessageList
|
||||
conversation={conversation}
|
||||
resolveMediaUrl={resolveMediaUrl}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { I18nProvider } from "@workspace/i18n"
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { ChatWorkspace } from "./chat-workspace"
|
||||
import { messages as englishMessages } from "./locales/en"
|
||||
import { messages as englishMessages } from "./locales/en-US"
|
||||
import type { ChatConversation, ChatSendHandler } from "./types"
|
||||
|
||||
vi.stubGlobal(
|
||||
@@ -72,7 +72,7 @@ function renderWorkspace({
|
||||
onSend?: ChatSendHandler
|
||||
} = {}) {
|
||||
return render(
|
||||
<I18nProvider locale="en" catalogs={{ en: englishMessages }}>
|
||||
<I18nProvider locale="en-US" catalogs={{ "en-US": englishMessages }}>
|
||||
<ChatWorkspace
|
||||
conversations={conversations}
|
||||
draft={draft}
|
||||
@@ -210,7 +210,7 @@ describe("ChatWorkspace", () => {
|
||||
expect(editor.getAttribute("contenteditable")).toBe("true")
|
||||
|
||||
rerender(
|
||||
<I18nProvider locale="en" catalogs={{ en: englishMessages }}>
|
||||
<I18nProvider locale="en-US" catalogs={{ "en-US": englishMessages }}>
|
||||
<ChatWorkspace conversations={conversations} draft="Lexical draft" />
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { chatMessages } from "../messages"
|
||||
import { chatCatalogLocales } from "./catalogs"
|
||||
import { messages as de } from "./de"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as es } from "./es"
|
||||
import { messages as fr } from "./fr"
|
||||
import { messages as ja } from "./ja"
|
||||
import { messages as ko } from "./ko"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
import { messages as zhHant } from "./zh-Hant"
|
||||
|
||||
describe("chat locale catalogs", () => {
|
||||
it("ship every chat message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
ko,
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
"zh-Hant": zhHant,
|
||||
},
|
||||
locales: chatCatalogLocales,
|
||||
messageIds: Object.values(chatMessages).map(
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "Dateien hinzufügen",
|
||||
"blocks.chats.actions.addEmoji": "Emoji hinzufügen",
|
||||
"blocks.chats.actions.addImage": "Bild hinzufügen",
|
||||
"blocks.chats.actions.backToConversations": "Zurück zu Unterhaltungen",
|
||||
"blocks.chats.actions.conversationDetails": "Unterhaltungsdetails",
|
||||
"blocks.chats.actions.loadEarlier": "Frühere Nachrichten laden",
|
||||
"blocks.chats.actions.removeAttachment": "{name} entfernen",
|
||||
"blocks.chats.actions.retry": "Erneut versuchen",
|
||||
"blocks.chats.actions.send": "Senden",
|
||||
"blocks.chats.actions.sendOnEnter": "Mit Enter senden",
|
||||
"blocks.chats.actions.sending": "Wird gesendet…",
|
||||
"blocks.chats.emptyConversations.description":
|
||||
"Starte eine Unterhaltung, damit sie hier erscheint.",
|
||||
"blocks.chats.emptyConversations.title": "Keine Unterhaltungen",
|
||||
"blocks.chats.emptyMessages.description":
|
||||
"Nachrichten dieser Unterhaltung werden hier angezeigt.",
|
||||
"blocks.chats.emptyMessages.title": "Noch keine Nachrichten",
|
||||
"blocks.chats.emptySearch": "Keine Unterhaltungen entsprechen deiner Suche.",
|
||||
"blocks.chats.emojiPicker.allEmojis": "Alle Emojis",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "Kürzlich verwendet",
|
||||
"blocks.chats.messagePlaceholder": "Nachricht schreiben…",
|
||||
"blocks.chats.messageStatus.delivered": "Zugestellt",
|
||||
"blocks.chats.messageStatus.failed": "Nicht gesendet",
|
||||
"blocks.chats.messageStatus.read": "Gelesen",
|
||||
"blocks.chats.messageStatus.sending": "Wird gesendet",
|
||||
"blocks.chats.messageStatus.sent": "Gesendet",
|
||||
"blocks.chats.search": "Unterhaltungen suchen",
|
||||
"blocks.chats.selectConversation": "Unterhaltung {title} auswählen",
|
||||
"blocks.chats.selectConversation.description":
|
||||
"Wähle eine Unterhaltung aus der Liste, um ihre Nachrichten zu lesen.",
|
||||
"blocks.chats.title": "Chats",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "Add files",
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "Añadir archivos",
|
||||
"blocks.chats.actions.addEmoji": "Añadir emoji",
|
||||
"blocks.chats.actions.addImage": "Añadir imagen",
|
||||
"blocks.chats.actions.backToConversations": "Volver a conversaciones",
|
||||
"blocks.chats.actions.conversationDetails": "Detalles de la conversación",
|
||||
"blocks.chats.actions.loadEarlier": "Cargar mensajes anteriores",
|
||||
"blocks.chats.actions.removeAttachment": "Quitar {name}",
|
||||
"blocks.chats.actions.retry": "Intentar de nuevo",
|
||||
"blocks.chats.actions.send": "Enviar",
|
||||
"blocks.chats.actions.sendOnEnter": "Enviar con Intro",
|
||||
"blocks.chats.actions.sending": "Enviando…",
|
||||
"blocks.chats.emptyConversations.description":
|
||||
"Inicia una conversación para verla aquí.",
|
||||
"blocks.chats.emptyConversations.title": "No hay conversaciones",
|
||||
"blocks.chats.emptyMessages.description":
|
||||
"Los mensajes de esta conversación aparecerán aquí.",
|
||||
"blocks.chats.emptyMessages.title": "Aún no hay mensajes",
|
||||
"blocks.chats.emptySearch":
|
||||
"No hay conversaciones que coincidan con tu búsqueda.",
|
||||
"blocks.chats.emojiPicker.allEmojis": "Todos los emojis",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "Usados recientemente",
|
||||
"blocks.chats.messagePlaceholder": "Escribe un mensaje…",
|
||||
"blocks.chats.messageStatus.delivered": "Entregado",
|
||||
"blocks.chats.messageStatus.failed": "No enviado",
|
||||
"blocks.chats.messageStatus.read": "Leído",
|
||||
"blocks.chats.messageStatus.sending": "Enviando",
|
||||
"blocks.chats.messageStatus.sent": "Enviado",
|
||||
"blocks.chats.search": "Buscar conversaciones",
|
||||
"blocks.chats.selectConversation": "Seleccionar conversación {title}",
|
||||
"blocks.chats.selectConversation.description":
|
||||
"Elige una conversación de la lista para leer sus mensajes.",
|
||||
"blocks.chats.title": "Chats",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "Ajouter des fichiers",
|
||||
"blocks.chats.actions.addEmoji": "Ajouter un emoji",
|
||||
"blocks.chats.actions.addImage": "Ajouter une image",
|
||||
"blocks.chats.actions.backToConversations": "Retour aux discussions",
|
||||
"blocks.chats.actions.conversationDetails": "Détails de la discussion",
|
||||
"blocks.chats.actions.loadEarlier": "Charger les messages précédents",
|
||||
"blocks.chats.actions.removeAttachment": "Retirer {name}",
|
||||
"blocks.chats.actions.retry": "Réessayer",
|
||||
"blocks.chats.actions.send": "Envoyer",
|
||||
"blocks.chats.actions.sendOnEnter": "Envoyer avec Entrée",
|
||||
"blocks.chats.actions.sending": "Envoi…",
|
||||
"blocks.chats.emptyConversations.description":
|
||||
"Démarrez une discussion pour la voir ici.",
|
||||
"blocks.chats.emptyConversations.title": "Aucune discussion",
|
||||
"blocks.chats.emptyMessages.description":
|
||||
"Les messages de cette discussion apparaîtront ici.",
|
||||
"blocks.chats.emptyMessages.title": "Pas encore de messages",
|
||||
"blocks.chats.emptySearch":
|
||||
"Aucune discussion ne correspond à votre recherche.",
|
||||
"blocks.chats.emojiPicker.allEmojis": "Tous les emojis",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "Récemment utilisés",
|
||||
"blocks.chats.messagePlaceholder": "Écrivez un message…",
|
||||
"blocks.chats.messageStatus.delivered": "Distribué",
|
||||
"blocks.chats.messageStatus.failed": "Non envoyé",
|
||||
"blocks.chats.messageStatus.read": "Lu",
|
||||
"blocks.chats.messageStatus.sending": "Envoi",
|
||||
"blocks.chats.messageStatus.sent": "Envoyé",
|
||||
"blocks.chats.search": "Rechercher des discussions",
|
||||
"blocks.chats.selectConversation": "Sélectionner la discussion {title}",
|
||||
"blocks.chats.selectConversation.description":
|
||||
"Choisissez une discussion dans la liste pour lire ses messages.",
|
||||
"blocks.chats.title": "Discussions",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "ファイルを追加",
|
||||
"blocks.chats.actions.addEmoji": "絵文字を追加",
|
||||
"blocks.chats.actions.addImage": "画像を追加",
|
||||
"blocks.chats.actions.backToConversations": "会話一覧に戻る",
|
||||
"blocks.chats.actions.conversationDetails": "会話の詳細",
|
||||
"blocks.chats.actions.loadEarlier": "以前のメッセージを読み込む",
|
||||
"blocks.chats.actions.removeAttachment": "{name} を削除",
|
||||
"blocks.chats.actions.retry": "再試行",
|
||||
"blocks.chats.actions.send": "送信",
|
||||
"blocks.chats.actions.sendOnEnter": "Enter キーで送信",
|
||||
"blocks.chats.actions.sending": "送信中…",
|
||||
"blocks.chats.emptyConversations.description":
|
||||
"会話を始めると、ここに表示されます。",
|
||||
"blocks.chats.emptyConversations.title": "会話はありません",
|
||||
"blocks.chats.emptyMessages.description":
|
||||
"この会話のメッセージはここに表示されます。",
|
||||
"blocks.chats.emptyMessages.title": "メッセージはまだありません",
|
||||
"blocks.chats.emptySearch": "検索に一致する会話はありません。",
|
||||
"blocks.chats.emojiPicker.allEmojis": "すべての絵文字",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "最近使用した絵文字",
|
||||
"blocks.chats.messagePlaceholder": "メッセージを入力…",
|
||||
"blocks.chats.messageStatus.delivered": "配信済み",
|
||||
"blocks.chats.messageStatus.failed": "未送信",
|
||||
"blocks.chats.messageStatus.read": "既読",
|
||||
"blocks.chats.messageStatus.sending": "送信中",
|
||||
"blocks.chats.messageStatus.sent": "送信済み",
|
||||
"blocks.chats.search": "会話を検索",
|
||||
"blocks.chats.selectConversation": "会話 {title} を選択",
|
||||
"blocks.chats.selectConversation.description":
|
||||
"一覧から会話を選択してメッセージを読みます。",
|
||||
"blocks.chats.title": "チャット",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "파일 추가",
|
||||
"blocks.chats.actions.addEmoji": "이모지 추가",
|
||||
"blocks.chats.actions.addImage": "이미지 추가",
|
||||
"blocks.chats.actions.backToConversations": "대화 목록으로 돌아가기",
|
||||
"blocks.chats.actions.conversationDetails": "대화 세부 정보",
|
||||
"blocks.chats.actions.loadEarlier": "이전 메시지 불러오기",
|
||||
"blocks.chats.actions.removeAttachment": "{name} 제거",
|
||||
"blocks.chats.actions.retry": "다시 시도",
|
||||
"blocks.chats.actions.send": "보내기",
|
||||
"blocks.chats.actions.sendOnEnter": "Enter 키로 전송",
|
||||
"blocks.chats.actions.sending": "보내는 중…",
|
||||
"blocks.chats.emptyConversations.description":
|
||||
"대화를 시작하면 여기에 표시됩니다.",
|
||||
"blocks.chats.emptyConversations.title": "대화가 없습니다",
|
||||
"blocks.chats.emptyMessages.description":
|
||||
"이 대화의 메시지가 여기에 표시됩니다.",
|
||||
"blocks.chats.emptyMessages.title": "아직 메시지가 없습니다",
|
||||
"blocks.chats.emptySearch": "검색과 일치하는 대화가 없습니다.",
|
||||
"blocks.chats.emojiPicker.allEmojis": "모든 이모지",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "최근 사용",
|
||||
"blocks.chats.messagePlaceholder": "메시지 작성…",
|
||||
"blocks.chats.messageStatus.delivered": "전달됨",
|
||||
"blocks.chats.messageStatus.failed": "전송되지 않음",
|
||||
"blocks.chats.messageStatus.read": "읽음",
|
||||
"blocks.chats.messageStatus.sending": "보내는 중",
|
||||
"blocks.chats.messageStatus.sent": "보냄",
|
||||
"blocks.chats.search": "대화 검색",
|
||||
"blocks.chats.selectConversation": "대화 {title} 선택",
|
||||
"blocks.chats.selectConversation.description":
|
||||
"목록에서 대화를 선택하여 메시지를 읽으세요.",
|
||||
"blocks.chats.title": "채팅",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { ChatMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
export const messages = {
|
||||
"blocks.chats.actions.addFiles": "新增檔案",
|
||||
"blocks.chats.actions.addEmoji": "新增表情",
|
||||
"blocks.chats.actions.addImage": "新增圖片",
|
||||
"blocks.chats.actions.backToConversations": "返回對話列表",
|
||||
"blocks.chats.actions.conversationDetails": "對話詳細資料",
|
||||
"blocks.chats.actions.loadEarlier": "載入較早訊息",
|
||||
"blocks.chats.actions.removeAttachment": "移除 {name}",
|
||||
"blocks.chats.actions.retry": "再試一次",
|
||||
"blocks.chats.actions.send": "傳送",
|
||||
"blocks.chats.actions.sendOnEnter": "按 Enter 傳送",
|
||||
"blocks.chats.actions.sending": "傳送中…",
|
||||
"blocks.chats.emptyConversations.description": "開始對話後會顯示在這裡。",
|
||||
"blocks.chats.emptyConversations.title": "沒有對話",
|
||||
"blocks.chats.emptyMessages.description": "此對話的訊息會顯示在這裡。",
|
||||
"blocks.chats.emptyMessages.title": "尚無訊息",
|
||||
"blocks.chats.emptySearch": "沒有符合搜尋條件的對話。",
|
||||
"blocks.chats.emojiPicker.allEmojis": "所有表情",
|
||||
"blocks.chats.emojiPicker.recentEmojis": "最近使用",
|
||||
"blocks.chats.messagePlaceholder": "輸入訊息…",
|
||||
"blocks.chats.messageStatus.delivered": "已送達",
|
||||
"blocks.chats.messageStatus.failed": "未傳送",
|
||||
"blocks.chats.messageStatus.read": "已讀",
|
||||
"blocks.chats.messageStatus.sending": "傳送中",
|
||||
"blocks.chats.messageStatus.sent": "已傳送",
|
||||
"blocks.chats.search": "搜尋對話",
|
||||
"blocks.chats.selectConversation": "選取對話 {title}",
|
||||
"blocks.chats.selectConversation.description": "從列表中選取對話以閱讀訊息。",
|
||||
"blocks.chats.title": "聊天",
|
||||
} as const satisfies ChatMessageCatalog
|
||||
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { layoutMessages } from "../messages"
|
||||
import { layoutCatalogLocales } from "./catalogs"
|
||||
import { messages as de } from "./de"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as es } from "./es"
|
||||
import { messages as fr } from "./fr"
|
||||
import { messages as ja } from "./ja"
|
||||
import { messages as ko } from "./ko"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
import { messages as zhHant } from "./zh-Hant"
|
||||
|
||||
describe("layout locale catalogs", () => {
|
||||
it("ship every layout message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
ko,
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
"zh-Hant": zhHant,
|
||||
},
|
||||
locales: layoutCatalogLocales,
|
||||
messageIds: Object.values(layoutMessages).map(
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "Breadcrumb-Menü öffnen",
|
||||
"blocks.refresh.description": "Daten der aktuellen Seite aktualisieren",
|
||||
"blocks.refresh.label": "Aktuelle Seitendaten aktualisieren",
|
||||
"blocks.userMenu.accountPassword": "Kontopasswort",
|
||||
"blocks.userMenu.appearance": "Darstellung",
|
||||
"blocks.userMenu.keybindings": "Tastenkürzel",
|
||||
"blocks.userMenu.logout": "Abmelden",
|
||||
"blocks.userMenu.open": "Benutzermenü öffnen",
|
||||
"blocks.userMenu.title": "Benutzermenü",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "Open breadcrumb menu",
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "Abrir el menú de ruta de navegación",
|
||||
"blocks.refresh.description": "Actualizar los datos de la página actual",
|
||||
"blocks.refresh.label": "Actualizar datos de la página actual",
|
||||
"blocks.userMenu.accountPassword": "Contraseña de la cuenta",
|
||||
"blocks.userMenu.appearance": "Apariencia",
|
||||
"blocks.userMenu.keybindings": "Atajos de teclado",
|
||||
"blocks.userMenu.logout": "Cerrar sesión",
|
||||
"blocks.userMenu.open": "Abrir menú de usuario",
|
||||
"blocks.userMenu.title": "Menú de usuario",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "Ouvrir le menu du fil d’Ariane",
|
||||
"blocks.refresh.description": "Actualiser les données de la page actuelle",
|
||||
"blocks.refresh.label": "Actualiser les données de la page",
|
||||
"blocks.userMenu.accountPassword": "Mot de passe du compte",
|
||||
"blocks.userMenu.appearance": "Apparence",
|
||||
"blocks.userMenu.keybindings": "Raccourcis clavier",
|
||||
"blocks.userMenu.logout": "Se déconnecter",
|
||||
"blocks.userMenu.open": "Ouvrir le menu utilisateur",
|
||||
"blocks.userMenu.title": "Menu utilisateur",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "パンくずメニューを開く",
|
||||
"blocks.refresh.description": "現在のページのデータを更新します",
|
||||
"blocks.refresh.label": "現在のページを更新",
|
||||
"blocks.userMenu.accountPassword": "アカウントのパスワード",
|
||||
"blocks.userMenu.appearance": "外観",
|
||||
"blocks.userMenu.keybindings": "キーバインド",
|
||||
"blocks.userMenu.logout": "ログアウト",
|
||||
"blocks.userMenu.open": "ユーザーメニューを開く",
|
||||
"blocks.userMenu.title": "ユーザーメニュー",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "이동 경로 메뉴 열기",
|
||||
"blocks.refresh.description": "현재 페이지 데이터를 새로 고칩니다",
|
||||
"blocks.refresh.label": "현재 페이지 데이터 새로 고침",
|
||||
"blocks.userMenu.accountPassword": "계정 비밀번호",
|
||||
"blocks.userMenu.appearance": "화면 모양",
|
||||
"blocks.userMenu.keybindings": "키 바인딩",
|
||||
"blocks.userMenu.logout": "로그아웃",
|
||||
"blocks.userMenu.open": "사용자 메뉴 열기",
|
||||
"blocks.userMenu.title": "사용자 메뉴",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { LayoutMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
export const messages = {
|
||||
"blocks.breadcrumb.menuLabel": "開啟麵包屑選單",
|
||||
"blocks.refresh.description": "重新整理目前頁面的資料",
|
||||
"blocks.refresh.label": "重新整理目前頁面資料",
|
||||
"blocks.userMenu.accountPassword": "帳號密碼",
|
||||
"blocks.userMenu.appearance": "介面外觀",
|
||||
"blocks.userMenu.keybindings": "按鍵綁定",
|
||||
"blocks.userMenu.logout": "登出",
|
||||
"blocks.userMenu.open": "開啟使用者選單",
|
||||
"blocks.userMenu.title": "使用者選單",
|
||||
} as const satisfies LayoutMessageCatalog
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, it } from "vitest"
|
||||
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { mediaMessages } from "../messages"
|
||||
import { mediaCatalogLocales } from "./catalogs"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
|
||||
describe("media locale catalogs", () => {
|
||||
it("ships every media message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
},
|
||||
locales: mediaCatalogLocales,
|
||||
messageIds: Object.values(mediaMessages).map(
|
||||
(descriptor) => descriptor.id
|
||||
),
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { mediaMessages } from "../messages"
|
||||
import type { BlockMessageCatalog } from "../../../i18n/catalogs"
|
||||
import {
|
||||
blockCatalogLocales,
|
||||
type BlockCatalogLocale,
|
||||
type BlockMessageCatalog,
|
||||
} from "../../../i18n/catalogs"
|
||||
|
||||
export { blockCatalogLocales as mediaCatalogLocales }
|
||||
export type MediaCatalogLocale = BlockCatalogLocale
|
||||
export type MediaMessageCatalog = BlockMessageCatalog<typeof mediaMessages>
|
||||
|
||||
+15
-8
@@ -1,6 +1,6 @@
|
||||
import type { MediaMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
export const messages = {
|
||||
"blocks.media.actions.cancel": "Cancel",
|
||||
@@ -21,10 +21,13 @@ export const messages = {
|
||||
"blocks.media.actions.unfavorite": "Unfavorite",
|
||||
"blocks.media.actions.upload": "Upload",
|
||||
"blocks.media.actions.viewOriginal": "View original",
|
||||
"blocks.media.copy.failed": "Copy failed. Check browser clipboard permissions.",
|
||||
"blocks.media.deleteAsset.description": "This media item will no longer appear in the library.",
|
||||
"blocks.media.copy.failed":
|
||||
"Copy failed. Check browser clipboard permissions.",
|
||||
"blocks.media.deleteAsset.description":
|
||||
"This media item will no longer appear in the library.",
|
||||
"blocks.media.deleteAsset.title": "Delete media",
|
||||
"blocks.media.deleteFolder.description": "This folder and its child folders will be deleted. Their media items will become unclassified.",
|
||||
"blocks.media.deleteFolder.description":
|
||||
"This folder and its child folders will be deleted. Their media items will become unclassified.",
|
||||
"blocks.media.deleteFolder.title": "Delete folder",
|
||||
"blocks.media.details.dimensions": "Dimensions",
|
||||
"blocks.media.details.fileName": "File name",
|
||||
@@ -32,7 +35,8 @@ export const messages = {
|
||||
"blocks.media.details.size": "Size",
|
||||
"blocks.media.details.type": "Type",
|
||||
"blocks.media.details.uploadedAt": "Uploaded",
|
||||
"blocks.media.empty.description": "Upload images or videos to manage them here.",
|
||||
"blocks.media.empty.description":
|
||||
"Upload images or videos to manage them here.",
|
||||
"blocks.media.empty.title": "No media",
|
||||
"blocks.media.emptySearch.description": "No media files match your search.",
|
||||
"blocks.media.filters.all": "All media",
|
||||
@@ -40,7 +44,8 @@ export const messages = {
|
||||
"blocks.media.filters.image": "Images",
|
||||
"blocks.media.filters.unclassified": "Unclassified",
|
||||
"blocks.media.filters.video": "Videos",
|
||||
"blocks.media.folders.classification": "Folders only organize media in this library and do not change file URLs.",
|
||||
"blocks.media.folders.classification":
|
||||
"Folders only organize media in this library and do not change file URLs.",
|
||||
"blocks.media.folders.create": "New folder",
|
||||
"blocks.media.folders.empty": "No folders yet. Create one.",
|
||||
"blocks.media.folders.name": "Folder name",
|
||||
@@ -50,11 +55,13 @@ export const messages = {
|
||||
"blocks.media.folders.title": "Folders",
|
||||
"blocks.media.load.error": "Unable to load media",
|
||||
"blocks.media.loading": "Loading media…",
|
||||
"blocks.media.picker.description": "Choose an uploaded image or video from the media library.",
|
||||
"blocks.media.picker.description":
|
||||
"Choose an uploaded image or video from the media library.",
|
||||
"blocks.media.picker.selectedCount": "{count} selected",
|
||||
"blocks.media.picker.title": "Select media",
|
||||
"blocks.media.picker.useSelected": "Use selected media",
|
||||
"blocks.media.renameMedia.description": "This changes the display name without changing the file or its URL.",
|
||||
"blocks.media.renameMedia.description":
|
||||
"This changes the display name without changing the file or its URL.",
|
||||
"blocks.media.renameMedia.title": "Rename media",
|
||||
"blocks.media.search": "Search media files",
|
||||
"blocks.media.title": "Media library",
|
||||
@@ -72,7 +72,7 @@ function MediaTestProvider({
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<I18nProvider locale="en">
|
||||
<I18nProvider locale="en-US">
|
||||
<MediaProvider adapter={adapter}>{children}</MediaProvider>
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { navigationMessages } from "../messages"
|
||||
import { navigationCatalogLocales } from "./catalogs"
|
||||
import { messages as de } from "./de"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as es } from "./es"
|
||||
import { messages as fr } from "./fr"
|
||||
import { messages as ja } from "./ja"
|
||||
import { messages as ko } from "./ko"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
import { messages as zhHant } from "./zh-Hant"
|
||||
|
||||
describe("navigation locale catalogs", () => {
|
||||
it("ship every navigation message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
ko,
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
"zh-Hant": zhHant,
|
||||
},
|
||||
locales: navigationCatalogLocales,
|
||||
messageIds: Object.values(navigationMessages).map(
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "Durch die Anwendung navigieren.",
|
||||
"blocks.navigation.open": "Navigation öffnen",
|
||||
"blocks.navigation.title": "Navigation",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "Navigate through the app.",
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "Navega por la aplicación.",
|
||||
"blocks.navigation.open": "Abrir navegación",
|
||||
"blocks.navigation.title": "Navegación",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "Naviguez dans l’application.",
|
||||
"blocks.navigation.open": "Ouvrir la navigation",
|
||||
"blocks.navigation.title": "Navigation",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "アプリ内のページを移動します。",
|
||||
"blocks.navigation.open": "ナビゲーションを開く",
|
||||
"blocks.navigation.title": "ナビゲーション",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "앱의 페이지를 탐색합니다.",
|
||||
"blocks.navigation.open": "탐색 메뉴 열기",
|
||||
"blocks.navigation.title": "탐색",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { NavigationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
export const messages = {
|
||||
"blocks.navigation.description": "瀏覽應用程式中的頁面。",
|
||||
"blocks.navigation.open": "開啟導覽",
|
||||
"blocks.navigation.title": "導覽",
|
||||
} as const satisfies NavigationMessageCatalog
|
||||
@@ -3,27 +3,15 @@ import { describe, it } from "vitest"
|
||||
import { expectCompleteCatalogs } from "../../../i18n/test-catalogs"
|
||||
import { notificationMessages } from "../messages"
|
||||
import { notificationCatalogLocales } from "./catalogs"
|
||||
import { messages as de } from "./de"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as es } from "./es"
|
||||
import { messages as fr } from "./fr"
|
||||
import { messages as ja } from "./ja"
|
||||
import { messages as ko } from "./ko"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
import { messages as zhHant } from "./zh-Hant"
|
||||
|
||||
describe("notification locale catalogs", () => {
|
||||
it("ship every notification message in every built-in locale", () => {
|
||||
expectCompleteCatalogs({
|
||||
catalogs: {
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
ko,
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
"zh-Hant": zhHant,
|
||||
},
|
||||
locales: notificationCatalogLocales,
|
||||
messageIds: Object.values(notificationMessages).map(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
export const messages = {
|
||||
"blocks.notifications.description":
|
||||
"Benachrichtigungen anzeigen und verwalten",
|
||||
"blocks.notifications.empty.description":
|
||||
"Neue Aktivitäten werden hier angezeigt.",
|
||||
"blocks.notifications.empty.title": "Keine Benachrichtigungen",
|
||||
"blocks.notifications.error.description":
|
||||
"Überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut.",
|
||||
"blocks.notifications.error.title":
|
||||
"Benachrichtigungen konnten nicht geladen werden",
|
||||
"blocks.notifications.justNow": "gerade eben",
|
||||
"blocks.notifications.loading": "Benachrichtigungen werden geladen…",
|
||||
"blocks.notifications.markAllAsRead": "Alle als gelesen markieren",
|
||||
"blocks.notifications.retry": "Erneut versuchen",
|
||||
"blocks.notifications.title": "Benachrichtigungen",
|
||||
"blocks.notifications.viewAll": "Alle anzeigen",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "View and manage notifications",
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "Ver y gestionar notificaciones",
|
||||
"blocks.notifications.empty.description":
|
||||
"La actividad nueva aparecerá aquí.",
|
||||
"blocks.notifications.empty.title": "No hay notificaciones",
|
||||
"blocks.notifications.error.description":
|
||||
"Comprueba la conexión de red e inténtalo de nuevo.",
|
||||
"blocks.notifications.error.title":
|
||||
"No se pudieron cargar las notificaciones",
|
||||
"blocks.notifications.justNow": "ahora mismo",
|
||||
"blocks.notifications.loading": "Cargando notificaciones…",
|
||||
"blocks.notifications.markAllAsRead": "Marcar todo como leído",
|
||||
"blocks.notifications.retry": "Intentar de nuevo",
|
||||
"blocks.notifications.title": "Notificaciones",
|
||||
"blocks.notifications.viewAll": "Ver todo",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "Afficher et gérer les notifications",
|
||||
"blocks.notifications.empty.description":
|
||||
"Les nouvelles activités apparaîtront ici.",
|
||||
"blocks.notifications.empty.title": "Aucune notification",
|
||||
"blocks.notifications.error.description":
|
||||
"Vérifiez votre connexion réseau et réessayez.",
|
||||
"blocks.notifications.error.title": "Impossible de charger les notifications",
|
||||
"blocks.notifications.justNow": "à l’instant",
|
||||
"blocks.notifications.loading": "Chargement des notifications…",
|
||||
"blocks.notifications.markAllAsRead": "Tout marquer comme lu",
|
||||
"blocks.notifications.retry": "Réessayer",
|
||||
"blocks.notifications.title": "Notifications",
|
||||
"blocks.notifications.viewAll": "Tout afficher",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "通知を表示・管理します",
|
||||
"blocks.notifications.empty.description":
|
||||
"新しいアクティビティがここに表示されます。",
|
||||
"blocks.notifications.empty.title": "通知はありません",
|
||||
"blocks.notifications.error.description":
|
||||
"ネットワーク接続を確認して、もう一度お試しください。",
|
||||
"blocks.notifications.error.title": "通知を読み込めませんでした",
|
||||
"blocks.notifications.justNow": "たった今",
|
||||
"blocks.notifications.loading": "通知を読み込み中…",
|
||||
"blocks.notifications.markAllAsRead": "すべて既読にする",
|
||||
"blocks.notifications.retry": "再試行",
|
||||
"blocks.notifications.title": "通知",
|
||||
"blocks.notifications.viewAll": "すべて表示",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "알림 보기 및 관리",
|
||||
"blocks.notifications.empty.description": "새 활동이 여기에 표시됩니다.",
|
||||
"blocks.notifications.empty.title": "알림 없음",
|
||||
"blocks.notifications.error.description":
|
||||
"네트워크 연결을 확인한 후 다시 시도하세요.",
|
||||
"blocks.notifications.error.title": "알림을 불러올 수 없음",
|
||||
"blocks.notifications.justNow": "방금",
|
||||
"blocks.notifications.loading": "알림 불러오는 중…",
|
||||
"blocks.notifications.markAllAsRead": "모두 읽음으로 표시",
|
||||
"blocks.notifications.retry": "다시 시도",
|
||||
"blocks.notifications.title": "알림",
|
||||
"blocks.notifications.viewAll": "모두 보기",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { NotificationMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
export const messages = {
|
||||
"blocks.notifications.description": "檢視並管理通知",
|
||||
"blocks.notifications.empty.description": "新的動態會顯示在這裡。",
|
||||
"blocks.notifications.empty.title": "沒有通知",
|
||||
"blocks.notifications.error.description": "請檢查網路連線後再試一次。",
|
||||
"blocks.notifications.error.title": "無法載入通知",
|
||||
"blocks.notifications.justNow": "剛剛",
|
||||
"blocks.notifications.loading": "正在載入通知…",
|
||||
"blocks.notifications.markAllAsRead": "全部標示為已讀",
|
||||
"blocks.notifications.retry": "再試一次",
|
||||
"blocks.notifications.title": "通知",
|
||||
"blocks.notifications.viewAll": "檢視全部",
|
||||
} as const satisfies NotificationMessageCatalog
|
||||
@@ -1,13 +1,4 @@
|
||||
export const blockCatalogLocales = [
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"ja",
|
||||
"ko",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const
|
||||
export const blockCatalogLocales = ["en-US", "zh-Hans"] as const
|
||||
|
||||
interface MessageDescriptorMap {
|
||||
readonly [key: string]: {
|
||||
|
||||
@@ -11,9 +11,13 @@ export function expectCompleteCatalogs({
|
||||
}) {
|
||||
expect(Object.keys(catalogs)).toEqual([...locales])
|
||||
|
||||
const expectedIds = [...messageIds].sort()
|
||||
// ES2022 compatibility: copy before sorting so the input remains immutable.
|
||||
// oxlint-disable-next-line unicorn/no-array-sort
|
||||
const expectedIds = Array.from(messageIds).sort()
|
||||
|
||||
for (const messages of Object.values(catalogs)) {
|
||||
// Object.keys returns a fresh array.
|
||||
// oxlint-disable-next-line unicorn/no-array-sort
|
||||
expect(Object.keys(messages).sort()).toEqual(expectedIds)
|
||||
}
|
||||
}
|
||||
|
||||
+13
-15
@@ -28,8 +28,8 @@ Create `i18n.config.json` in that application:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceLocale": "en",
|
||||
"locales": ["en", "zh-Hans"],
|
||||
"sourceLocale": "en-US",
|
||||
"locales": ["en-US", "zh-Hans"],
|
||||
"catalogPath": "src/locales/{locale}/messages",
|
||||
"catalogSources": [
|
||||
"@workspace/ui/locales/{locale}",
|
||||
@@ -96,7 +96,7 @@ function Root({
|
||||
<I18nProvider
|
||||
locale={locale}
|
||||
locales={[
|
||||
{ locale: "en", label: "English" },
|
||||
{ locale: "en-US", label: "English" },
|
||||
{ locale: "zh-Hans", label: "简体中文" },
|
||||
]}
|
||||
catalogs={{ [locale]: messages }}
|
||||
@@ -126,22 +126,22 @@ function Greeting() {
|
||||
}
|
||||
```
|
||||
|
||||
`@workspace/ui` and every `@workspace/blocks` block provide `de`, `en`, `es`,
|
||||
`fr`, `ja`, `ko`, `zh-Hans`, and `zh-Hant` as independent locale subpaths.
|
||||
Import only the blocks and languages enabled by the application:
|
||||
`@workspace/ui`, `@workspace/lexical`, and every `@workspace/blocks` block
|
||||
provide only `en-US` and `zh-Hans` as independent locale subpaths. Import only
|
||||
the blocks and language enabled by the application:
|
||||
|
||||
```ts
|
||||
import {
|
||||
calendarLocale,
|
||||
messages as uiJapanese,
|
||||
} from "@workspace/ui/locales/ja"
|
||||
import { messages as navigationJapanese } from "@workspace/blocks/navigation/locales/ja"
|
||||
messages as uiEnglish,
|
||||
} from "@workspace/ui/locales/en-US"
|
||||
import { messages as navigationEnglish } from "@workspace/blocks/navigation/locales/en-US"
|
||||
```
|
||||
|
||||
The UI root `locales` entry and each block-specific `locales` entry export
|
||||
metadata and types only. They do not statically import catalogs. For example,
|
||||
using `@workspace/blocks/navigation` and
|
||||
`@workspace/blocks/navigation/locales/ja` does not import appearance, chats,
|
||||
`@workspace/blocks/navigation/locales/en-US` does not import appearance, chats,
|
||||
layout, notifications, or their translations. The UI locale entry also
|
||||
exposes its matching `react-day-picker` `calendarLocale`.
|
||||
|
||||
@@ -182,7 +182,7 @@ and available locales from the provider, while the Vite `i18n()` plugin serves
|
||||
catalog reads, updates, extraction, and compilation.
|
||||
|
||||
```tsx
|
||||
<I18nProvider locale="zh-Hans" locales={["en", "zh-Hans"]}>
|
||||
<I18nProvider locale="zh-Hans" locales={["en-US", "zh-Hans"]}>
|
||||
<App />
|
||||
{import.meta.env.DEV && <I18nDevtool dark={"ui\\:dark"} />}
|
||||
</I18nProvider>
|
||||
@@ -205,8 +205,7 @@ Pass a BCP 47 language tag with `locale` to select it explicitly:
|
||||
|
||||
When `locale` is omitted, the Devtool selects the first supported entry from
|
||||
`navigator.languages` and falls back to English. Built-in control-panel
|
||||
locales are English, German, Spanish, French, Japanese, Korean, Simplified
|
||||
Chinese, and Traditional Chinese.
|
||||
locales are English (`en-US`) and Simplified Chinese (`zh-Hans`).
|
||||
|
||||
Lower-level `MessagePanel`, `MessageRepositoryProvider`, and repository types
|
||||
remain available from `@workspace/i18n/devtool`.
|
||||
@@ -222,7 +221,6 @@ Run inside the consuming application so the CLI discovers that application's
|
||||
`i18n.config.json`:
|
||||
|
||||
```sh
|
||||
bun run i18n new ja
|
||||
bun run i18n extract
|
||||
bun run i18n compile
|
||||
bun run i18n ui
|
||||
@@ -249,4 +247,4 @@ bun run i18n:compile
|
||||
|
||||
The application's `i18n.config.json` is the single machine-editable source of
|
||||
truth. Runtime catalog access, Devtool actions, extraction, and compilation all
|
||||
derive their Lingui configuration from it.
|
||||
derive their Lingui configuration from it.
|
||||
@@ -26,33 +26,39 @@ async function createProject() {
|
||||
catalogPath: "app/{locale}/messages",
|
||||
catalogSources: ["./dependency/{locale}.js"],
|
||||
include: ["src"],
|
||||
locales: ["en", "zh-Hans"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US", "zh-Hans"],
|
||||
sourceLocale: "en-US",
|
||||
}
|
||||
|
||||
temporaryDirectories.push(projectRoot)
|
||||
|
||||
for (const locale of project.locales) {
|
||||
const dependencyDirectory = join(projectRoot, "dependency")
|
||||
const applicationDirectory = join(projectRoot, "app", locale)
|
||||
await Promise.all(
|
||||
project.locales.map(async (locale) => {
|
||||
const dependencyDirectory = join(projectRoot, "dependency")
|
||||
const applicationDirectory = join(projectRoot, "app", locale)
|
||||
|
||||
await mkdir(dependencyDirectory, { recursive: true })
|
||||
await mkdir(applicationDirectory, { recursive: true })
|
||||
await writeFile(
|
||||
join(dependencyDirectory, `${locale}.js`),
|
||||
`export const messages = ${JSON.stringify({
|
||||
dependency: `${locale}: dependency`,
|
||||
overridden: `${locale}: dependency`,
|
||||
})}\n`
|
||||
)
|
||||
await writeFile(
|
||||
join(applicationDirectory, "messages.js"),
|
||||
`export const messages = ${JSON.stringify({
|
||||
application: `${locale}: application`,
|
||||
overridden: `${locale}: application`,
|
||||
})}\n`
|
||||
)
|
||||
}
|
||||
await Promise.all([
|
||||
mkdir(dependencyDirectory, { recursive: true }),
|
||||
mkdir(applicationDirectory, { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
join(dependencyDirectory, `${locale}.js`),
|
||||
`export const messages = ${JSON.stringify({
|
||||
dependency: `${locale}: dependency`,
|
||||
overridden: `${locale}: dependency`,
|
||||
})}\n`
|
||||
),
|
||||
writeFile(
|
||||
join(applicationDirectory, "messages.js"),
|
||||
`export const messages = ${JSON.stringify({
|
||||
application: `${locale}: application`,
|
||||
overridden: `${locale}: application`,
|
||||
})}\n`
|
||||
),
|
||||
])
|
||||
})
|
||||
)
|
||||
|
||||
return { project, projectRoot }
|
||||
}
|
||||
@@ -63,12 +69,12 @@ describe("production catalog sources", () => {
|
||||
const filenames = await resolveCatalogSourceFilenames(
|
||||
project,
|
||||
projectRoot,
|
||||
"en"
|
||||
"en-US"
|
||||
)
|
||||
|
||||
expect(filenames).toEqual([
|
||||
join(projectRoot, "dependency", "en.js"),
|
||||
join(projectRoot, "app", "en", "messages.js"),
|
||||
join(projectRoot, "dependency", "en-US.js"),
|
||||
join(projectRoot, "app", "en-US", "messages.js"),
|
||||
])
|
||||
})
|
||||
|
||||
@@ -76,10 +82,10 @@ describe("production catalog sources", () => {
|
||||
const { project, projectRoot } = await createProject()
|
||||
const { catalogs } = await loadProjectCatalogs(project, projectRoot)
|
||||
|
||||
expect(catalogs.en).toEqual({
|
||||
application: "en: application",
|
||||
dependency: "en: dependency",
|
||||
overridden: "en: application",
|
||||
expect(catalogs["en-US"]).toEqual({
|
||||
application: "en-US: application",
|
||||
dependency: "en-US: dependency",
|
||||
overridden: "en-US: application",
|
||||
})
|
||||
expect(catalogs["zh-Hans"]).toEqual({
|
||||
application: "zh-Hans: application",
|
||||
|
||||
@@ -30,12 +30,10 @@ export async function resolveCompiledCatalogFilename(
|
||||
? pathname
|
||||
: resolve(projectRoot, pathname)
|
||||
const candidates = [baseFilename, `${baseFilename}.ts`, `${baseFilename}.js`]
|
||||
const availability = await Promise.all(candidates.map(fileExists))
|
||||
const candidate = candidates.find((_, index) => availability[index])
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (await fileExists(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
if (candidate) return candidate
|
||||
|
||||
throw new Error(
|
||||
`Compiled i18n catalog not found for ${locale}: ${baseFilename}.{ts,js}. Run the i18n compile command before building.`
|
||||
|
||||
@@ -23,7 +23,8 @@ export function createMessageIdSchema(
|
||||
): MessageIdSchema {
|
||||
const ids = Array.from(
|
||||
new Set(Object.values(catalogs).flatMap((catalog) => Object.keys(catalog)))
|
||||
).sort()
|
||||
)
|
||||
ids.sort()
|
||||
const messages: Record<string, string> = {}
|
||||
const idsByHash = new Map<string, string>()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http"
|
||||
import type { ViteDevServer } from "vite"
|
||||
import type { Connect, ViteDevServer } from "vite"
|
||||
|
||||
import type { UpdateMessageInput } from "../devtool/types"
|
||||
import { CatalogService } from "./catalog-service.ts"
|
||||
@@ -43,94 +43,93 @@ export function configureI18nApi(
|
||||
server: ViteDevServer,
|
||||
projectFilename: string
|
||||
) {
|
||||
server.middlewares.use(async (request, response, next) => {
|
||||
if (!request.url) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const url = new URL(request.url, "http://localhost")
|
||||
|
||||
if (!url.pathname.startsWith(API_PREFIX)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
request.method === "GET" &&
|
||||
url.pathname === `${API_PREFIX}/project`
|
||||
) {
|
||||
const project = await readProject(projectFilename)
|
||||
sendJson(response, 200, project)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "GET" &&
|
||||
url.pathname === `${API_PREFIX}/messages`
|
||||
) {
|
||||
const locale = url.searchParams.get("locale")
|
||||
|
||||
if (!locale) {
|
||||
sendJson(response, 400, { error: "locale is required." })
|
||||
return
|
||||
}
|
||||
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.getMessages(locale))
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "GET" &&
|
||||
url.pathname === `${API_PREFIX}/packages`
|
||||
) {
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.getPackages())
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "PUT" &&
|
||||
url.pathname === `${API_PREFIX}/messages`
|
||||
) {
|
||||
const input = await readJsonBody(request)
|
||||
|
||||
if (!isUpdateMessageInput(input)) {
|
||||
sendJson(response, 400, {
|
||||
error: "The message update payload is invalid.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.updateMessage(input))
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "POST" &&
|
||||
url.pathname.startsWith(`${API_PREFIX}/actions/`)
|
||||
) {
|
||||
const action = url.pathname.slice(`${API_PREFIX}/actions/`.length)
|
||||
|
||||
if (action !== "extract" && action !== "compile") {
|
||||
sendJson(response, 404, { error: "Unknown action." })
|
||||
return
|
||||
}
|
||||
|
||||
const result = await runProjectLinguiCommand(projectFilename, action)
|
||||
sendJson(response, 200, result)
|
||||
return
|
||||
}
|
||||
|
||||
sendJson(response, 404, { error: "Not found." })
|
||||
} catch (error) {
|
||||
sendJson(response, 500, {
|
||||
error:
|
||||
error instanceof Error ? error.message : "An unknown error occurred.",
|
||||
})
|
||||
}
|
||||
server.middlewares.use((request, response, next) => {
|
||||
void handleI18nApiRequest(request, response, next, projectFilename).catch(
|
||||
next
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function handleI18nApiRequest(
|
||||
request: IncomingMessage,
|
||||
response: ServerResponse,
|
||||
next: Connect.NextFunction,
|
||||
projectFilename: string
|
||||
) {
|
||||
if (!request.url) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const url = new URL(request.url, "http://localhost")
|
||||
|
||||
if (!url.pathname.startsWith(API_PREFIX)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (request.method === "GET" && url.pathname === `${API_PREFIX}/project`) {
|
||||
const project = await readProject(projectFilename)
|
||||
sendJson(response, 200, project)
|
||||
return
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === `${API_PREFIX}/messages`) {
|
||||
const locale = url.searchParams.get("locale")
|
||||
|
||||
if (!locale) {
|
||||
sendJson(response, 400, { error: "locale is required." })
|
||||
return
|
||||
}
|
||||
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.getMessages(locale))
|
||||
return
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === `${API_PREFIX}/packages`) {
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.getPackages())
|
||||
return
|
||||
}
|
||||
|
||||
if (request.method === "PUT" && url.pathname === `${API_PREFIX}/messages`) {
|
||||
const input = await readJsonBody(request)
|
||||
|
||||
if (!isUpdateMessageInput(input)) {
|
||||
sendJson(response, 400, {
|
||||
error: "The message update payload is invalid.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const service = new CatalogService(projectFilename)
|
||||
sendJson(response, 200, await service.updateMessage(input))
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "POST" &&
|
||||
url.pathname.startsWith(`${API_PREFIX}/actions/`)
|
||||
) {
|
||||
const action = url.pathname.slice(`${API_PREFIX}/actions/`.length)
|
||||
|
||||
if (action !== "extract" && action !== "compile") {
|
||||
sendJson(response, 404, { error: "Unknown action." })
|
||||
return
|
||||
}
|
||||
|
||||
const result = await runProjectLinguiCommand(projectFilename, action)
|
||||
sendJson(response, 200, result)
|
||||
return
|
||||
}
|
||||
|
||||
sendJson(response, 404, { error: "Not found." })
|
||||
} catch (error) {
|
||||
sendJson(response, 500, {
|
||||
error:
|
||||
error instanceof Error ? error.message : "An unknown error occurred.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ describe("CatalogService", () => {
|
||||
{
|
||||
catalogPath: "locales/{locale}/messages",
|
||||
include: ["src"],
|
||||
locales: ["en", "zh-Hans"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US", "zh-Hans"],
|
||||
sourceLocale: "en-US",
|
||||
},
|
||||
null,
|
||||
2
|
||||
@@ -54,7 +54,7 @@ describe("CatalogService", () => {
|
||||
throw new Error("Expected a Lingui catalog.")
|
||||
}
|
||||
|
||||
await catalog.write("en", {
|
||||
await catalog.write("en-US", {
|
||||
"dashboard.title": {
|
||||
comments: ["Dashboard heading"],
|
||||
message: "Dashboard",
|
||||
@@ -113,7 +113,7 @@ describe("CatalogService", () => {
|
||||
'msgstr "数据看板"'
|
||||
)
|
||||
|
||||
const sourceMessages = await service.getMessages("en")
|
||||
const sourceMessages = await service.getMessages("en-US")
|
||||
const applicationMessage = sourceMessages.find(
|
||||
(item) => item.id === "dashboard.title"
|
||||
)
|
||||
@@ -134,7 +134,7 @@ describe("CatalogService", () => {
|
||||
service.updateMessage({
|
||||
catalog: applicationMessage?.catalog ?? "messages",
|
||||
id: "dashboard.title",
|
||||
locale: "en",
|
||||
locale: "en-US",
|
||||
translation: "Customized dashboard",
|
||||
})
|
||||
).rejects.toThrow("must be changed in source code")
|
||||
@@ -142,7 +142,7 @@ describe("CatalogService", () => {
|
||||
const customizedDependency = await service.updateMessage({
|
||||
catalog: dependencyMessage?.catalog ?? "messages",
|
||||
id: "ui.common.close",
|
||||
locale: "en",
|
||||
locale: "en-US",
|
||||
translation: "Dismiss",
|
||||
})
|
||||
|
||||
@@ -151,7 +151,7 @@ describe("CatalogService", () => {
|
||||
source: "Close",
|
||||
translation: "Dismiss",
|
||||
})
|
||||
expect(await readFile(catalog.getFilename("en"), "utf8")).toContain(
|
||||
expect(await readFile(catalog.getFilename("en-US"), "utf8")).toContain(
|
||||
'msgstr "Dismiss"'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -37,15 +37,16 @@ class PackageNameResolver {
|
||||
}
|
||||
|
||||
async resolve(origins: MessageType["origin"] | undefined) {
|
||||
for (const [filename] of origins ?? []) {
|
||||
const packageName = await this.#findNearestPackageName(
|
||||
dirname(resolve(this.#projectRoot, filename))
|
||||
const packageNames = await Promise.all(
|
||||
(origins ?? []).map(([filename]) =>
|
||||
this.#findNearestPackageName(
|
||||
dirname(resolve(this.#projectRoot, filename))
|
||||
)
|
||||
)
|
||||
)
|
||||
const packageName = packageNames.find(Boolean)
|
||||
|
||||
if (packageName) {
|
||||
return packageName
|
||||
}
|
||||
}
|
||||
if (packageName) return packageName
|
||||
|
||||
return this.#projectPackageName
|
||||
}
|
||||
@@ -187,27 +188,28 @@ export class CatalogService {
|
||||
...Object.keys(targetCatalog),
|
||||
])
|
||||
|
||||
return Promise.all(
|
||||
Array.from(ids)
|
||||
.sort((left, right) => left.localeCompare(right))
|
||||
.map(async (id) => {
|
||||
const sourceEntry = sourceCatalog[id]
|
||||
const targetEntry = targetCatalog[id]
|
||||
const packageName = await packageNameResolver.resolve(
|
||||
sourceEntry?.origin ?? targetEntry?.origin
|
||||
)
|
||||
const sortedIds = Array.from(ids)
|
||||
sortedIds.sort((left, right) => left.localeCompare(right))
|
||||
|
||||
return toDevtoolMessage({
|
||||
applicationPackageName,
|
||||
catalog: key,
|
||||
id,
|
||||
locale,
|
||||
packageName,
|
||||
sourceEntry,
|
||||
sourceLocale: config.sourceLocale,
|
||||
targetEntry,
|
||||
})
|
||||
return Promise.all(
|
||||
sortedIds.map(async (id) => {
|
||||
const sourceEntry = sourceCatalog[id]
|
||||
const targetEntry = targetCatalog[id]
|
||||
const packageName = await packageNameResolver.resolve(
|
||||
sourceEntry?.origin ?? targetEntry?.origin
|
||||
)
|
||||
|
||||
return toDevtoolMessage({
|
||||
applicationPackageName,
|
||||
catalog: key,
|
||||
id,
|
||||
locale,
|
||||
packageName,
|
||||
sourceEntry,
|
||||
sourceLocale: config.sourceLocale,
|
||||
targetEntry,
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -30,8 +30,8 @@ describe("runProjectLinguiCommand", () => {
|
||||
{
|
||||
catalogPath: "locales/{locale}/messages",
|
||||
include: ["src"],
|
||||
locales: ["en"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US"],
|
||||
sourceLocale: "en-US",
|
||||
},
|
||||
null,
|
||||
2
|
||||
@@ -45,7 +45,7 @@ describe("runProjectLinguiCommand", () => {
|
||||
throw new Error("Expected a Lingui catalog.")
|
||||
}
|
||||
|
||||
await catalog.write("en", {
|
||||
await catalog.write("en-US", {
|
||||
greeting: {
|
||||
message: "Hello",
|
||||
translation: "Hello",
|
||||
@@ -53,7 +53,7 @@ describe("runProjectLinguiCommand", () => {
|
||||
})
|
||||
|
||||
const result = await runProjectLinguiCommand(projectFilename, "compile")
|
||||
const compiledFilename = join(directory, "locales/en/messages.ts")
|
||||
const compiledFilename = join(directory, "locales/en-US/messages.ts")
|
||||
const firstModifiedTime = (await stat(compiledFilename)).mtimeMs
|
||||
|
||||
expect(result.output).toContain("Compiling message catalogs")
|
||||
|
||||
@@ -83,24 +83,26 @@ async function readFileIfPresent(filename: string) {
|
||||
async function publishCompiledCatalogs(
|
||||
catalogs: readonly StagedCompiledCatalog[]
|
||||
) {
|
||||
for (const catalog of catalogs) {
|
||||
const nextContent = await readFile(catalog.stagedOutputFilename, "utf8")
|
||||
const currentContent = await readFileIfPresent(catalog.outputFilename)
|
||||
await Promise.all(
|
||||
catalogs.map(async (catalog) => {
|
||||
const [nextContent, currentContent] = await Promise.all([
|
||||
readFile(catalog.stagedOutputFilename, "utf8"),
|
||||
readFileIfPresent(catalog.outputFilename),
|
||||
])
|
||||
|
||||
if (currentContent === nextContent) {
|
||||
continue
|
||||
}
|
||||
if (currentContent === nextContent) return
|
||||
|
||||
await mkdir(dirname(catalog.outputFilename), { recursive: true })
|
||||
await writeFile(catalog.outputFilename, nextContent, "utf8")
|
||||
}
|
||||
await mkdir(dirname(catalog.outputFilename), { recursive: true })
|
||||
await writeFile(catalog.outputFilename, nextContent, "utf8")
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function runLinguiCommand(
|
||||
args: readonly string[],
|
||||
cwd: string
|
||||
): Promise<LinguiCommandResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn("node", [LINGUI_BIN, ...args], {
|
||||
cwd,
|
||||
env: process.env,
|
||||
@@ -119,7 +121,7 @@ async function runLinguiCommand(
|
||||
child.on("error", reject)
|
||||
child.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ output })
|
||||
resolvePromise({ output })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -170,7 +172,7 @@ export async function runProjectLinguiCommand(
|
||||
catalogs: projectConfig.catalogs?.map((catalog) =>
|
||||
typeof catalog === "string"
|
||||
? catalog
|
||||
: { ...catalog, path: stagedCatalogPath }
|
||||
: Object.assign({}, catalog, { path: stagedCatalogPath })
|
||||
),
|
||||
}
|
||||
: undefined),
|
||||
|
||||
@@ -62,8 +62,8 @@ describe("readProject", () => {
|
||||
"@workspace/blocks/navigation/locales/{locale}",
|
||||
],
|
||||
include: ["src"],
|
||||
locales: ["en", "zh-Hans"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US", "zh-Hans"],
|
||||
sourceLocale: "en-US",
|
||||
})}\n`
|
||||
)
|
||||
|
||||
|
||||
@@ -60,24 +60,16 @@ function validateProjectConfig(
|
||||
}
|
||||
}
|
||||
|
||||
async function findInParents(startDirectory: string) {
|
||||
let directory = resolve(startDirectory)
|
||||
async function findInParents(
|
||||
startDirectory: string
|
||||
): Promise<string | undefined> {
|
||||
const directory = resolve(startDirectory)
|
||||
const candidate = join(directory, PROJECT_FILENAME)
|
||||
|
||||
while (true) {
|
||||
const candidate = join(directory, PROJECT_FILENAME)
|
||||
if (await fileExists(candidate)) return candidate
|
||||
|
||||
if (await fileExists(candidate)) {
|
||||
return candidate
|
||||
}
|
||||
|
||||
const parent = dirname(directory)
|
||||
|
||||
if (parent === directory) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
directory = parent
|
||||
}
|
||||
const parent = dirname(directory)
|
||||
return parent === directory ? undefined : findInParents(parent)
|
||||
}
|
||||
|
||||
export async function resolveProjectFilename(
|
||||
|
||||
@@ -13,8 +13,8 @@ describe("createLinguiConfig", () => {
|
||||
catalogSources: ["@workspace/ui/locales/{locale}"],
|
||||
exclude: ["**/*.test.ts"],
|
||||
include: ["src"],
|
||||
locales: ["en", "zh-Hans"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US", "zh-Hans"],
|
||||
sourceLocale: "en-US",
|
||||
})
|
||||
).toEqual({
|
||||
catalogs: [
|
||||
@@ -24,11 +24,11 @@ describe("createLinguiConfig", () => {
|
||||
path: "<rootDir>/locales/{locale}/messages",
|
||||
},
|
||||
],
|
||||
locales: ["en", "zh-Hans"],
|
||||
locales: ["en-US", "zh-Hans"],
|
||||
runtimeConfigModule: {
|
||||
Trans: ["@workspace/i18n", "Translate"],
|
||||
},
|
||||
sourceLocale: "en",
|
||||
sourceLocale: "en-US",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,8 +37,8 @@ describe("createLinguiConfig", () => {
|
||||
{
|
||||
catalogPath: "locales/{locale}/messages",
|
||||
include: ["src"],
|
||||
locales: ["en"],
|
||||
sourceLocale: "en",
|
||||
locales: ["en-US"],
|
||||
sourceLocale: "en-US",
|
||||
},
|
||||
"/workspace/apps/web"
|
||||
)
|
||||
|
||||
@@ -4,16 +4,16 @@ import { describe, expect, it } from "vitest"
|
||||
|
||||
import { AnsiText, resolveAnsiComponent } from "./ansi-text"
|
||||
|
||||
function TestComponent({ children }: { children?: string }) {
|
||||
return <code>{children}</code>
|
||||
}
|
||||
|
||||
describe("AnsiText", () => {
|
||||
it("resolves direct, CommonJS, and nested default exports", () => {
|
||||
const Component = ({ children }: { children?: string }) => (
|
||||
<code>{children}</code>
|
||||
)
|
||||
|
||||
expect(resolveAnsiComponent(Component)).toBe(Component)
|
||||
expect(resolveAnsiComponent({ default: Component })).toBe(Component)
|
||||
expect(resolveAnsiComponent({ default: { default: Component } })).toBe(
|
||||
Component
|
||||
expect(resolveAnsiComponent(TestComponent)).toBe(TestComponent)
|
||||
expect(resolveAnsiComponent({ default: TestComponent })).toBe(TestComponent)
|
||||
expect(resolveAnsiComponent({ default: { default: TestComponent } })).toBe(
|
||||
TestComponent
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export function DevtoolApp() {
|
||||
|
||||
const locale =
|
||||
state.project.locales.find(
|
||||
(locale) => locale !== state.project.sourceLocale
|
||||
(candidateLocale) => candidateLocale !== state.project.sourceLocale
|
||||
) ?? state.project.sourceLocale
|
||||
|
||||
if (!state.project.locales.includes(locale)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { StrictMode } from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
import { DevtoolApp } from "./app"
|
||||
// oxlint-disable-next-line import/no-unassigned-import -- Loads the standalone Devtool stylesheet.
|
||||
import "./styles.css"
|
||||
|
||||
const root = document.getElementById("root")
|
||||
|
||||
@@ -5,17 +5,18 @@ import { resolveI18nDevtoolLocale } from "./devtool-localization"
|
||||
describe("resolveI18nDevtoolLocale", () => {
|
||||
it("normalizes common BCP 47 language tags", () => {
|
||||
expect(resolveI18nDevtoolLocale("zh-CN")).toBe("zh-Hans")
|
||||
expect(resolveI18nDevtoolLocale("zh-TW")).toBe("zh-Hant")
|
||||
expect(resolveI18nDevtoolLocale("es-419")).toBe("es")
|
||||
expect(resolveI18nDevtoolLocale("en-GB")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("selects the first supported preferred locale", () => {
|
||||
expect(
|
||||
resolveI18nDevtoolLocale(undefined, ["pt-BR", "fr-CA", "en-US"])
|
||||
).toBe("fr")
|
||||
resolveI18nDevtoolLocale(undefined, ["pt-BR", "fr-CA", "zh-CN"])
|
||||
).toBe("zh-Hans")
|
||||
})
|
||||
|
||||
it("falls back to English", () => {
|
||||
expect(resolveI18nDevtoolLocale(undefined, ["pt-BR"])).toBe("en")
|
||||
expect(resolveI18nDevtoolLocale(undefined, ["pt-BR", "zh-TW"])).toBe(
|
||||
"en-US"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
export const I18N_DEVTOOL_LOCALES = [
|
||||
"en",
|
||||
"de",
|
||||
"es",
|
||||
"fr",
|
||||
"ja",
|
||||
"ko",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const
|
||||
export const I18N_DEVTOOL_LOCALES = ["en-US", "zh-Hans"] as const
|
||||
|
||||
export type I18nDevtoolLocale = (typeof I18N_DEVTOOL_LOCALES)[number]
|
||||
|
||||
@@ -104,7 +95,7 @@ interface DevtoolMessages {
|
||||
}
|
||||
|
||||
const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
en: {
|
||||
"en-US": {
|
||||
actions: {
|
||||
compile: "Compile",
|
||||
compileCompleted: "Compilation completed.",
|
||||
@@ -195,460 +186,6 @@ const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
open: "Open I18n Devtool",
|
||||
},
|
||||
},
|
||||
de: {
|
||||
actions: {
|
||||
compile: "Kompilieren",
|
||||
compileCompleted: "Kompilierung abgeschlossen.",
|
||||
compileFailed: "Kompilierung fehlgeschlagen.",
|
||||
compiling: "Wird kompiliert…",
|
||||
enterFullscreen: "Vollbildmodus aktivieren",
|
||||
exitFullscreen: "Vollbildmodus beenden",
|
||||
extract: "Extrahieren",
|
||||
extractCompleted: "Extraktion abgeschlossen.",
|
||||
extractFailed: "Extraktion fehlgeschlagen.",
|
||||
extracting: "Wird extrahiert…",
|
||||
label: "Aktionen",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Befehlsausgabe schließen",
|
||||
collapse: "Befehlsausgabe einklappen",
|
||||
expand: "Befehlsausgabe ausklappen",
|
||||
title: "Befehlsausgabe",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Zielsprache der Nachrichten",
|
||||
label: "Zielsprache",
|
||||
noTargetLocales: "Keine Zielsprachen konfiguriert.",
|
||||
sourceLabel: "Ausgangssprache",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `${count} weitere Quellorte`,
|
||||
chooseCopyContent: "Inhalt zum Kopieren auswählen",
|
||||
collapseNamespace: (path) => `${path} einklappen`,
|
||||
collapsePackage: (packageName) => `${packageName} einklappen`,
|
||||
copied: "Kopiert",
|
||||
copyDescriptor: "Nachrichten-Deskriptor kopieren",
|
||||
copyFailed: "Kopieren fehlgeschlagen",
|
||||
copyMessageId: "Nachrichten-ID kopieren",
|
||||
copySource: "Quelltext kopieren",
|
||||
copySourceLocation: "Quellort kopieren",
|
||||
copyTranslation: "Aktuelle Übersetzung kopieren",
|
||||
couldNotSave: "Speichern fehlgeschlagen",
|
||||
emptyCatalog:
|
||||
"Es sind noch keine Nachrichten verfügbar. Extrahieren Sie das Projekt, um diesen Katalog zu füllen.",
|
||||
emptyPackage: "In diesem Paket wurden keine Nachrichten gefunden.",
|
||||
expandNamespace: (path) => `${path} ausklappen`,
|
||||
expandPackage: (packageName) => `${packageName} ausklappen`,
|
||||
loading: (locale) => `${locale} wird geladen…`,
|
||||
messageCount: (count) => `${count} Nachrichten`,
|
||||
messageEditor: "Nachrichteneditor",
|
||||
messageNavigation: "Nachrichtennavigation",
|
||||
missingCount: (count) => `${count} fehlen`,
|
||||
missingOnly: "Nur fehlende",
|
||||
noMatches: "Keine Nachrichten entsprechen den aktuellen Filtern.",
|
||||
noMissingMessages: "Alle Nachrichten sind übersetzt.",
|
||||
noSearchResults: "Keine Nachrichten entsprechen Ihrer Suche.",
|
||||
reset: "Zurücksetzen",
|
||||
retry: "Erneut versuchen",
|
||||
save: "Speichern",
|
||||
saved: "Gespeichert",
|
||||
saving: "Wird gespeichert…",
|
||||
search: "Nachrichten durchsuchen",
|
||||
selectMessage:
|
||||
"Wählen Sie eine Nachricht aus, um ihre Übersetzung anzuzeigen und zu bearbeiten.",
|
||||
source: "Quelle",
|
||||
sourceLocations: "Quellorte",
|
||||
translation: "Übersetzung",
|
||||
translationFor: (id, locale) => `${id}: Übersetzung für ${locale}`,
|
||||
unknownSourceLocation: "Unbekannter Quellort",
|
||||
},
|
||||
settings: {
|
||||
auto: "Automatisch",
|
||||
autoTheme: "Automatisches Design",
|
||||
center: "Mitte",
|
||||
dark: "Dunkel",
|
||||
darkTheme: "Dunkles Design",
|
||||
interfaceLanguage: "Oberflächensprache",
|
||||
interfaceLanguageControl: "Oberflächensprache des I18n Devtools",
|
||||
label: "I18n-Devtool-Einstellungen",
|
||||
left: "Links",
|
||||
light: "Hell",
|
||||
lightTheme: "Helles Design",
|
||||
open: "I18n-Devtool-Einstellungen öffnen",
|
||||
panelPosition: "Panelposition",
|
||||
panelPositionControl: "Position des I18n-Devtool-Panels",
|
||||
right: "Rechts",
|
||||
theme: "Design",
|
||||
themeControl: "I18n-Devtool-Design",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool schließen",
|
||||
open: "I18n Devtool öffnen",
|
||||
},
|
||||
},
|
||||
es: {
|
||||
actions: {
|
||||
compile: "Compilar",
|
||||
compileCompleted: "Compilación completada.",
|
||||
compileFailed: "Error de compilación.",
|
||||
compiling: "Compilando…",
|
||||
enterFullscreen: "Entrar en pantalla completa",
|
||||
exitFullscreen: "Salir de pantalla completa",
|
||||
extract: "Extraer",
|
||||
extractCompleted: "Extracción completada.",
|
||||
extractFailed: "Error de extracción.",
|
||||
extracting: "Extrayendo…",
|
||||
label: "Acciones",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Cerrar salida del comando",
|
||||
collapse: "Contraer salida del comando",
|
||||
expand: "Expandir salida del comando",
|
||||
title: "Salida del comando",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Idioma de destino de los mensajes",
|
||||
label: "Idioma de destino",
|
||||
noTargetLocales: "No hay idiomas de destino configurados.",
|
||||
sourceLabel: "Idioma de origen",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) =>
|
||||
`${count} ubicaciones de origen adicionales`,
|
||||
chooseCopyContent: "Elegir contenido para copiar",
|
||||
collapseNamespace: (path) => `Contraer ${path}`,
|
||||
collapsePackage: (packageName) => `Contraer ${packageName}`,
|
||||
copied: "Copiado",
|
||||
copyDescriptor: "Copiar descriptor del mensaje",
|
||||
copyFailed: "No se pudo copiar",
|
||||
copyMessageId: "Copiar ID del mensaje",
|
||||
copySource: "Copiar texto de origen",
|
||||
copySourceLocation: "Copiar ubicación de origen",
|
||||
copyTranslation: "Copiar traducción actual",
|
||||
couldNotSave: "No se pudo guardar",
|
||||
emptyCatalog:
|
||||
"Aún no hay mensajes disponibles. Extraiga el proyecto para completar este catálogo.",
|
||||
emptyPackage: "No se encontraron mensajes en este paquete.",
|
||||
expandNamespace: (path) => `Expandir ${path}`,
|
||||
expandPackage: (packageName) => `Expandir ${packageName}`,
|
||||
loading: (locale) => `Cargando ${locale}…`,
|
||||
messageCount: (count) => `${count} mensajes`,
|
||||
messageEditor: "Editor de mensajes",
|
||||
messageNavigation: "Navegación de mensajes",
|
||||
missingCount: (count) => `${count} faltantes`,
|
||||
missingOnly: "Solo faltantes",
|
||||
noMatches: "Ningún mensaje coincide con los filtros actuales.",
|
||||
noMissingMessages: "Todos los mensajes tienen traducción.",
|
||||
noSearchResults: "Ningún mensaje coincide con la búsqueda.",
|
||||
reset: "Restablecer",
|
||||
retry: "Reintentar",
|
||||
save: "Guardar",
|
||||
saved: "Guardado",
|
||||
saving: "Guardando…",
|
||||
search: "Buscar mensajes",
|
||||
selectMessage: "Seleccione un mensaje para ver y editar su traducción.",
|
||||
source: "Origen",
|
||||
sourceLocations: "Ubicaciones de origen",
|
||||
translation: "Traducción",
|
||||
translationFor: (id, locale) => `Traducción de ${id} para ${locale}`,
|
||||
unknownSourceLocation: "Ubicación de origen desconocida",
|
||||
},
|
||||
settings: {
|
||||
auto: "Automático",
|
||||
autoTheme: "Tema automático",
|
||||
center: "Centro",
|
||||
dark: "Oscuro",
|
||||
darkTheme: "Tema oscuro",
|
||||
interfaceLanguage: "Idioma de la interfaz",
|
||||
interfaceLanguageControl: "Idioma de la interfaz de I18n Devtool",
|
||||
label: "Ajustes de I18n Devtool",
|
||||
left: "Izquierda",
|
||||
light: "Claro",
|
||||
lightTheme: "Tema claro",
|
||||
open: "Abrir ajustes de I18n Devtool",
|
||||
panelPosition: "Posición del panel",
|
||||
panelPositionControl: "Posición del panel de I18n Devtool",
|
||||
right: "Derecha",
|
||||
theme: "Tema",
|
||||
themeControl: "Tema de I18n Devtool",
|
||||
},
|
||||
trigger: {
|
||||
close: "Cerrar I18n Devtool",
|
||||
open: "Abrir I18n Devtool",
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
actions: {
|
||||
compile: "Compiler",
|
||||
compileCompleted: "Compilation terminée.",
|
||||
compileFailed: "Échec de la compilation.",
|
||||
compiling: "Compilation…",
|
||||
enterFullscreen: "Passer en plein écran",
|
||||
exitFullscreen: "Quitter le plein écran",
|
||||
extract: "Extraire",
|
||||
extractCompleted: "Extraction terminée.",
|
||||
extractFailed: "Échec de l’extraction.",
|
||||
extracting: "Extraction…",
|
||||
label: "Actions",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Fermer la sortie de commande",
|
||||
collapse: "Réduire la sortie de commande",
|
||||
expand: "Développer la sortie de commande",
|
||||
title: "Sortie de commande",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Langue cible des messages",
|
||||
label: "Langue cible",
|
||||
noTargetLocales: "Aucune langue cible n’est configurée.",
|
||||
sourceLabel: "Langue source",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) =>
|
||||
`${count} emplacements source supplémentaires`,
|
||||
chooseCopyContent: "Choisir le contenu à copier",
|
||||
collapseNamespace: (path) => `Réduire ${path}`,
|
||||
collapsePackage: (packageName) => `Réduire ${packageName}`,
|
||||
copied: "Copié",
|
||||
copyDescriptor: "Copier le descripteur du message",
|
||||
copyFailed: "Échec de la copie",
|
||||
copyMessageId: "Copier l’identifiant du message",
|
||||
copySource: "Copier le texte source",
|
||||
copySourceLocation: "Copier l’emplacement source",
|
||||
copyTranslation: "Copier la traduction actuelle",
|
||||
couldNotSave: "Échec de l’enregistrement",
|
||||
emptyCatalog:
|
||||
"Aucun message n’est encore disponible. Extrayez le projet pour remplir ce catalogue.",
|
||||
emptyPackage: "Aucun message n’a été trouvé dans ce package.",
|
||||
expandNamespace: (path) => `Développer ${path}`,
|
||||
expandPackage: (packageName) => `Développer ${packageName}`,
|
||||
loading: (locale) => `Chargement de ${locale}…`,
|
||||
messageCount: (count) => `${count} messages`,
|
||||
messageEditor: "Éditeur de messages",
|
||||
messageNavigation: "Navigation des messages",
|
||||
missingCount: (count) => `${count} manquants`,
|
||||
missingOnly: "Manquants uniquement",
|
||||
noMatches: "Aucun message ne correspond aux filtres actuels.",
|
||||
noMissingMessages: "Tous les messages sont traduits.",
|
||||
noSearchResults: "Aucun message ne correspond à votre recherche.",
|
||||
reset: "Réinitialiser",
|
||||
retry: "Réessayer",
|
||||
save: "Enregistrer",
|
||||
saved: "Enregistré",
|
||||
saving: "Enregistrement…",
|
||||
search: "Rechercher des messages",
|
||||
selectMessage:
|
||||
"Sélectionnez un message pour afficher et modifier sa traduction.",
|
||||
source: "Source",
|
||||
sourceLocations: "Emplacements source",
|
||||
translation: "Traduction",
|
||||
translationFor: (id, locale) => `Traduction de ${id} pour ${locale}`,
|
||||
unknownSourceLocation: "Emplacement source inconnu",
|
||||
},
|
||||
settings: {
|
||||
auto: "Auto",
|
||||
autoTheme: "Thème automatique",
|
||||
center: "Centre",
|
||||
dark: "Sombre",
|
||||
darkTheme: "Thème sombre",
|
||||
interfaceLanguage: "Langue de l’interface",
|
||||
interfaceLanguageControl: "Langue de l’interface de l’outil I18n",
|
||||
label: "Paramètres de I18n Devtool",
|
||||
left: "Gauche",
|
||||
light: "Clair",
|
||||
lightTheme: "Thème clair",
|
||||
open: "Ouvrir les paramètres de I18n Devtool",
|
||||
panelPosition: "Position du panneau",
|
||||
panelPositionControl: "Position du panneau I18n Devtool",
|
||||
right: "Droite",
|
||||
theme: "Thème",
|
||||
themeControl: "Thème de I18n Devtool",
|
||||
},
|
||||
trigger: {
|
||||
close: "Fermer I18n Devtool",
|
||||
open: "Ouvrir I18n Devtool",
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
actions: {
|
||||
compile: "コンパイル",
|
||||
compileCompleted: "コンパイルが完了しました。",
|
||||
compileFailed: "コンパイルに失敗しました。",
|
||||
compiling: "コンパイル中…",
|
||||
enterFullscreen: "全画面表示にする",
|
||||
exitFullscreen: "全画面表示を終了",
|
||||
extract: "抽出",
|
||||
extractCompleted: "抽出が完了しました。",
|
||||
extractFailed: "抽出に失敗しました。",
|
||||
extracting: "抽出中…",
|
||||
label: "操作",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "コマンド出力を閉じる",
|
||||
collapse: "コマンド出力を折りたたむ",
|
||||
expand: "コマンド出力を展開する",
|
||||
title: "コマンド出力",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "メッセージの翻訳先言語",
|
||||
label: "翻訳先言語",
|
||||
noTargetLocales: "翻訳先言語が設定されていません。",
|
||||
sourceLabel: "原文言語",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `他 ${count} 件のソース位置`,
|
||||
chooseCopyContent: "コピーする内容を選択",
|
||||
collapseNamespace: (path) => `${path} を折りたたむ`,
|
||||
collapsePackage: (packageName) => `${packageName} を折りたたむ`,
|
||||
copied: "コピーしました",
|
||||
copyDescriptor: "メッセージ記述子をコピー",
|
||||
copyFailed: "コピーできませんでした",
|
||||
copyMessageId: "メッセージ ID をコピー",
|
||||
copySource: "原文をコピー",
|
||||
copySourceLocation: "ソース位置をコピー",
|
||||
copyTranslation: "現在の翻訳をコピー",
|
||||
couldNotSave: "保存できませんでした",
|
||||
emptyCatalog:
|
||||
"メッセージはまだありません。プロジェクトを抽出してカタログを作成してください。",
|
||||
emptyPackage: "このパッケージにはメッセージがありません。",
|
||||
expandNamespace: (path) => `${path} を展開`,
|
||||
expandPackage: (packageName) => `${packageName} を展開`,
|
||||
loading: (locale) => `${locale} を読み込み中…`,
|
||||
messageCount: (count) => `${count} 件`,
|
||||
messageEditor: "メッセージエディター",
|
||||
messageNavigation: "メッセージナビゲーション",
|
||||
missingCount: (count) => `未翻訳 ${count} 件`,
|
||||
missingOnly: "未翻訳のみ",
|
||||
noMatches: "現在のフィルターに一致するメッセージはありません。",
|
||||
noMissingMessages: "すべてのメッセージが翻訳済みです。",
|
||||
noSearchResults: "検索に一致するメッセージはありません。",
|
||||
reset: "リセット",
|
||||
retry: "再試行",
|
||||
save: "保存",
|
||||
saved: "保存済み",
|
||||
saving: "保存中…",
|
||||
search: "メッセージを検索",
|
||||
selectMessage: "翻訳を表示・編集するメッセージを選択してください。",
|
||||
source: "原文",
|
||||
sourceLocations: "ソース位置",
|
||||
translation: "翻訳",
|
||||
translationFor: (id, locale) => `${id} の ${locale} 翻訳`,
|
||||
unknownSourceLocation: "ソース位置不明",
|
||||
},
|
||||
settings: {
|
||||
auto: "自動",
|
||||
autoTheme: "自動テーマ",
|
||||
center: "中央",
|
||||
dark: "ダーク",
|
||||
darkTheme: "ダークテーマ",
|
||||
interfaceLanguage: "表示言語",
|
||||
interfaceLanguageControl: "I18n Devtool の表示言語",
|
||||
label: "I18n Devtool の設定",
|
||||
left: "左",
|
||||
light: "ライト",
|
||||
lightTheme: "ライトテーマ",
|
||||
open: "I18n Devtool の設定を開く",
|
||||
panelPosition: "パネル位置",
|
||||
panelPositionControl: "I18n Devtool のパネル位置",
|
||||
right: "右",
|
||||
theme: "テーマ",
|
||||
themeControl: "I18n Devtool のテーマ",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool を閉じる",
|
||||
open: "I18n Devtool を開く",
|
||||
},
|
||||
},
|
||||
ko: {
|
||||
actions: {
|
||||
compile: "컴파일",
|
||||
compileCompleted: "컴파일이 완료되었습니다.",
|
||||
compileFailed: "컴파일에 실패했습니다.",
|
||||
compiling: "컴파일 중…",
|
||||
enterFullscreen: "전체 화면으로 전환",
|
||||
exitFullscreen: "전체 화면 종료",
|
||||
extract: "추출",
|
||||
extractCompleted: "추출이 완료되었습니다.",
|
||||
extractFailed: "추출에 실패했습니다.",
|
||||
extracting: "추출 중…",
|
||||
label: "작업",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "명령 출력을 닫기",
|
||||
collapse: "명령 출력을 접기",
|
||||
expand: "명령 출력을 펼치기",
|
||||
title: "명령 출력",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "메시지 대상 언어",
|
||||
label: "대상 언어",
|
||||
noTargetLocales: "대상 언어가 설정되지 않았습니다.",
|
||||
sourceLabel: "원본 언어",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `추가 소스 위치 ${count}개`,
|
||||
chooseCopyContent: "복사할 내용 선택",
|
||||
collapseNamespace: (path) => `${path} 접기`,
|
||||
collapsePackage: (packageName) => `${packageName} 접기`,
|
||||
copied: "복사됨",
|
||||
copyDescriptor: "메시지 설명자 복사",
|
||||
copyFailed: "복사하지 못했습니다",
|
||||
copyMessageId: "메시지 ID 복사",
|
||||
copySource: "원문 복사",
|
||||
copySourceLocation: "소스 위치 복사",
|
||||
copyTranslation: "현재 번역 복사",
|
||||
couldNotSave: "저장하지 못했습니다",
|
||||
emptyCatalog:
|
||||
"아직 사용할 수 있는 메시지가 없습니다. 프로젝트에서 메시지를 추출해 카탈로그를 채우세요.",
|
||||
emptyPackage: "이 패키지에는 메시지가 없습니다.",
|
||||
expandNamespace: (path) => `${path} 펼치기`,
|
||||
expandPackage: (packageName) => `${packageName} 펼치기`,
|
||||
loading: (locale) => `${locale} 불러오는 중…`,
|
||||
messageCount: (count) => `${count}개 메시지`,
|
||||
messageEditor: "메시지 편집기",
|
||||
messageNavigation: "메시지 탐색",
|
||||
missingCount: (count) => `${count}개 누락`,
|
||||
missingOnly: "누락된 항목만",
|
||||
noMatches: "현재 필터와 일치하는 메시지가 없습니다.",
|
||||
noMissingMessages: "모든 메시지가 번역되었습니다.",
|
||||
noSearchResults: "검색어와 일치하는 메시지가 없습니다.",
|
||||
reset: "재설정",
|
||||
retry: "다시 시도",
|
||||
save: "저장",
|
||||
saved: "저장됨",
|
||||
saving: "저장 중…",
|
||||
search: "메시지 검색",
|
||||
selectMessage: "번역을 확인하고 편집할 메시지를 선택하세요.",
|
||||
source: "원문",
|
||||
sourceLocations: "소스 위치",
|
||||
translation: "번역",
|
||||
translationFor: (id, locale) => `${id}의 ${locale} 번역`,
|
||||
unknownSourceLocation: "알 수 없는 소스 위치",
|
||||
},
|
||||
settings: {
|
||||
auto: "자동",
|
||||
autoTheme: "자동 테마",
|
||||
center: "가운데",
|
||||
dark: "다크",
|
||||
darkTheme: "다크 테마",
|
||||
interfaceLanguage: "인터페이스 언어",
|
||||
interfaceLanguageControl: "I18n Devtool 인터페이스 언어",
|
||||
label: "I18n Devtool 설정",
|
||||
left: "왼쪽",
|
||||
light: "라이트",
|
||||
lightTheme: "라이트 테마",
|
||||
open: "I18n Devtool 설정 열기",
|
||||
panelPosition: "패널 위치",
|
||||
panelPositionControl: "I18n Devtool 패널 위치",
|
||||
right: "오른쪽",
|
||||
theme: "테마",
|
||||
themeControl: "I18n Devtool 테마",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool 닫기",
|
||||
open: "I18n Devtool 열기",
|
||||
},
|
||||
},
|
||||
"zh-Hans": {
|
||||
actions: {
|
||||
compile: "编译",
|
||||
@@ -738,103 +275,14 @@ const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
open: "打开 I18n 开发工具",
|
||||
},
|
||||
},
|
||||
"zh-Hant": {
|
||||
actions: {
|
||||
compile: "編譯",
|
||||
compileCompleted: "編譯完成。",
|
||||
compileFailed: "編譯失敗。",
|
||||
compiling: "正在編譯…",
|
||||
enterFullscreen: "進入全螢幕",
|
||||
exitFullscreen: "退出全螢幕",
|
||||
extract: "擷取",
|
||||
extractCompleted: "擷取完成。",
|
||||
extractFailed: "擷取失敗。",
|
||||
extracting: "正在擷取…",
|
||||
label: "操作",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "關閉命令輸出",
|
||||
collapse: "收合命令輸出",
|
||||
expand: "展開命令輸出",
|
||||
title: "命令輸出",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "訊息目標語言",
|
||||
label: "目標語言",
|
||||
noTargetLocales: "尚未設定目標語言。",
|
||||
sourceLabel: "來源語言",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `另外 ${count} 個來源位置`,
|
||||
chooseCopyContent: "選擇要複製的內容",
|
||||
collapseNamespace: (path) => `收合 ${path}`,
|
||||
collapsePackage: (packageName) => `收合 ${packageName}`,
|
||||
copied: "已複製",
|
||||
copyDescriptor: "複製 Message Descriptor",
|
||||
copyFailed: "複製失敗",
|
||||
copyMessageId: "複製 Message ID",
|
||||
copySource: "複製原文",
|
||||
copySourceLocation: "複製來源位置",
|
||||
copyTranslation: "複製目前譯文",
|
||||
couldNotSave: "儲存失敗",
|
||||
emptyCatalog: "尚無可用訊息。請先擷取專案訊息以產生目錄。",
|
||||
emptyPackage: "這個套件中暫無訊息。",
|
||||
expandNamespace: (path) => `展開 ${path}`,
|
||||
expandPackage: (packageName) => `展開 ${packageName}`,
|
||||
loading: (locale) => `正在載入 ${locale}…`,
|
||||
messageCount: (count) => `共 ${count} 筆`,
|
||||
messageEditor: "訊息編輯器",
|
||||
messageNavigation: "訊息導覽",
|
||||
missingCount: (count) => `缺漏 ${count} 筆`,
|
||||
missingOnly: "僅顯示缺漏項目",
|
||||
noMatches: "沒有符合目前篩選條件的訊息。",
|
||||
noMissingMessages: "所有訊息皆已完成翻譯。",
|
||||
noSearchResults: "沒有符合搜尋內容的訊息。",
|
||||
reset: "重設",
|
||||
retry: "重試",
|
||||
save: "儲存",
|
||||
saved: "已儲存",
|
||||
saving: "正在儲存…",
|
||||
search: "搜尋訊息",
|
||||
selectMessage: "請選擇一則訊息以檢視和編輯譯文。",
|
||||
source: "原文",
|
||||
sourceLocations: "來源位置",
|
||||
translation: "譯文",
|
||||
translationFor: (id, locale) => `${id} 的 ${locale} 翻譯`,
|
||||
unknownSourceLocation: "未知來源位置",
|
||||
},
|
||||
settings: {
|
||||
auto: "自動",
|
||||
autoTheme: "自動主題",
|
||||
center: "置中",
|
||||
dark: "深色",
|
||||
darkTheme: "深色主題",
|
||||
interfaceLanguage: "介面語言",
|
||||
interfaceLanguageControl: "I18n 開發工具介面語言",
|
||||
label: "I18n 開發工具設定",
|
||||
left: "左側",
|
||||
light: "淺色",
|
||||
lightTheme: "淺色主題",
|
||||
open: "開啟 I18n 開發工具設定",
|
||||
panelPosition: "面板位置",
|
||||
panelPositionControl: "I18n 開發工具面板位置",
|
||||
right: "右側",
|
||||
theme: "主題",
|
||||
themeControl: "I18n 開發工具主題",
|
||||
},
|
||||
trigger: {
|
||||
close: "關閉 I18n 開發工具",
|
||||
open: "開啟 I18n 開發工具",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const defaultContextValue: {
|
||||
locale: I18nDevtoolLocale
|
||||
messages: DevtoolMessages
|
||||
} = {
|
||||
locale: "en",
|
||||
messages: messagesByLocale.en,
|
||||
locale: "en-US",
|
||||
messages: messagesByLocale["en-US"],
|
||||
}
|
||||
|
||||
const DevtoolLocalizationContext = React.createContext(defaultContextValue)
|
||||
@@ -847,30 +295,22 @@ function matchDevtoolLocale(locale: string): I18nDevtoolLocale | undefined {
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedLocale === "zh-hant" ||
|
||||
normalizedLocale.startsWith("zh-hant-")
|
||||
normalizedLocale === "zh" ||
|
||||
normalizedLocale === "zh-hans" ||
|
||||
normalizedLocale.startsWith("zh-hans-") ||
|
||||
normalizedLocale === "zh-cn" ||
|
||||
normalizedLocale.startsWith("zh-cn-") ||
|
||||
normalizedLocale === "zh-sg" ||
|
||||
normalizedLocale.startsWith("zh-sg-")
|
||||
) {
|
||||
return "zh-Hant"
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedLocale === "zh-tw" ||
|
||||
normalizedLocale.startsWith("zh-tw-") ||
|
||||
normalizedLocale === "zh-hk" ||
|
||||
normalizedLocale.startsWith("zh-hk-") ||
|
||||
normalizedLocale === "zh-mo" ||
|
||||
normalizedLocale.startsWith("zh-mo-")
|
||||
) {
|
||||
return "zh-Hant"
|
||||
}
|
||||
|
||||
if (normalizedLocale === "zh" || normalizedLocale.startsWith("zh-")) {
|
||||
return "zh-Hans"
|
||||
}
|
||||
|
||||
const language = normalizedLocale.split("-")[0]
|
||||
if (normalizedLocale === "en" || normalizedLocale.startsWith("en-")) {
|
||||
return "en-US"
|
||||
}
|
||||
|
||||
return I18N_DEVTOOL_LOCALES.find((candidate) => candidate === language)
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getNavigatorLocales() {
|
||||
@@ -899,7 +339,7 @@ export function resolveI18nDevtoolLocale(
|
||||
}
|
||||
}
|
||||
|
||||
return "en"
|
||||
return "en-US"
|
||||
}
|
||||
|
||||
export function getI18nDevtoolMessages(locale: I18nDevtoolLocale) {
|
||||
|
||||
@@ -24,7 +24,7 @@ afterEach(() => {
|
||||
|
||||
function renderDevtool(props?: I18nDevtoolProps) {
|
||||
return render(
|
||||
<I18nProvider catalogs={{ en: {} }} locale="en" locales={["en"]}>
|
||||
<I18nProvider catalogs={{ "en-US": {} }} locale="en-US" locales={["en-US"]}>
|
||||
<I18nDevtool dark="ui:dark" {...props} />
|
||||
</I18nProvider>
|
||||
)
|
||||
@@ -87,17 +87,17 @@ describe("I18nDevtool", () => {
|
||||
it("detects the first supported navigator locale", async () => {
|
||||
vi.spyOn(window.navigator, "languages", "get").mockReturnValue([
|
||||
"pt-BR",
|
||||
"ja-JP",
|
||||
"fr-CA",
|
||||
"en-US",
|
||||
])
|
||||
renderDevtool()
|
||||
|
||||
const trigger = await screen.findByRole("button", {
|
||||
name: "I18n Devtool を開く",
|
||||
name: "Open I18n Devtool",
|
||||
})
|
||||
const devtool = trigger.closest<HTMLElement>('[data-slot="i18n-devtool"]')
|
||||
|
||||
expect(devtool?.getAttribute("lang")).toBe("ja")
|
||||
expect(devtool?.getAttribute("lang")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("falls back to English when an explicit locale is unsupported", async () => {
|
||||
@@ -109,7 +109,7 @@ describe("I18nDevtool", () => {
|
||||
})
|
||||
const devtool = trigger.closest<HTMLElement>('[data-slot="i18n-devtool"]')
|
||||
|
||||
expect(devtool?.getAttribute("lang")).toBe("en")
|
||||
expect(devtool?.getAttribute("lang")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("includes the project source locale as a customization target", async () => {
|
||||
@@ -118,7 +118,7 @@ describe("I18nDevtool", () => {
|
||||
vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input)
|
||||
const body = url.endsWith("/project")
|
||||
? { locales: ["en", "zh-Hans"], sourceLocale: "en" }
|
||||
? { locales: ["en-US", "zh-Hans"], sourceLocale: "en-US" }
|
||||
: []
|
||||
|
||||
return new Response(JSON.stringify(body), {
|
||||
@@ -130,10 +130,10 @@ describe("I18nDevtool", () => {
|
||||
|
||||
render(
|
||||
<I18nProvider
|
||||
catalogs={{ en: {}, "zh-Hans": {} }}
|
||||
locale="en"
|
||||
catalogs={{ "en-US": {}, "zh-Hans": {} }}
|
||||
locale="en-US"
|
||||
locales={[
|
||||
{ label: "English", locale: "en" },
|
||||
{ label: "English", locale: "en-US" },
|
||||
{ label: "简体中文", locale: "zh-Hans" },
|
||||
]}
|
||||
>
|
||||
@@ -146,7 +146,7 @@ describe("I18nDevtool", () => {
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(localeSelect).toHaveProperty("value", "en")
|
||||
expect(localeSelect).toHaveProperty("value", "en-US")
|
||||
})
|
||||
expect(screen.getByText("Source locale")).toBeTruthy()
|
||||
expect(screen.getByText("English", { selector: "output" })).toBeTruthy()
|
||||
@@ -157,9 +157,9 @@ describe("I18nDevtool", () => {
|
||||
it("tracks the configured document dark-mode class", async () => {
|
||||
render(
|
||||
<I18nProvider
|
||||
catalogs={{ en: {} }}
|
||||
locale="en"
|
||||
locales={["en", "zh-Hans"]}
|
||||
catalogs={{ "en-US": {} }}
|
||||
locale="en-US"
|
||||
locales={["en-US", "zh-Hans"]}
|
||||
>
|
||||
<I18nDevtool dark={"ui\\:dark"} />
|
||||
</I18nProvider>
|
||||
|
||||
@@ -24,7 +24,7 @@ const message: DevtoolMessage = {
|
||||
origins: [{ file: "src/navigation.tsx", line: 12 }],
|
||||
packageName: "@workspace/ui",
|
||||
source: "Home",
|
||||
sourceLocale: "en",
|
||||
sourceLocale: "en-US",
|
||||
translation: "",
|
||||
}
|
||||
|
||||
@@ -310,10 +310,10 @@ describe("MessagePanel", () => {
|
||||
updateMessage,
|
||||
}
|
||||
|
||||
render(<MessagePanel locale="en" repository={repository} />)
|
||||
render(<MessagePanel locale="en-US" repository={repository} />)
|
||||
|
||||
const textarea = await screen.findByLabelText(
|
||||
"navigation.home translation for en"
|
||||
"navigation.home translation for en-US"
|
||||
)
|
||||
|
||||
expect(textarea).toHaveProperty("readOnly", true)
|
||||
|
||||
@@ -62,14 +62,13 @@ function normalizeLocales(
|
||||
const definition = typeof input === "string" ? { locale: input } : input
|
||||
const languageTag = definition.languageTag ?? definition.locale
|
||||
|
||||
return {
|
||||
...definition,
|
||||
return Object.assign({}, definition, {
|
||||
direction: definition.direction ?? inferLocaleDirection(languageTag),
|
||||
label:
|
||||
definition.label ??
|
||||
getLocaleDisplayName(languageTag, definition.locale),
|
||||
languageTag,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!definitions.some((definition) => definition.locale === locale)) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { LocalizedText } from "./localized-text"
|
||||
import { Translate } from "./translate"
|
||||
|
||||
const catalogs = {
|
||||
en: {
|
||||
"en-US": {
|
||||
greeting: "Hello",
|
||||
},
|
||||
"zh-Hans": {
|
||||
@@ -39,6 +39,43 @@ function HookProbe() {
|
||||
)
|
||||
}
|
||||
|
||||
function DirectionProbe() {
|
||||
return <span>{useLocales()[0]?.direction}</span>
|
||||
}
|
||||
|
||||
function LocaleLabelsProbe() {
|
||||
return (
|
||||
<span>
|
||||
{useLocales()
|
||||
.map((locale) => locale.label)
|
||||
.join("|")}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function LocaleProbe() {
|
||||
const locale = useLocaleDefinition()
|
||||
|
||||
return (
|
||||
<span>
|
||||
{locale.locale}|{locale.languageTag}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function FormatterProbe() {
|
||||
const formatters = useFormatters()
|
||||
|
||||
return (
|
||||
<output>
|
||||
{formatters.formatNumber(1234.5)}|
|
||||
{formatters.formatCurrency(1234.5, "USD")}|
|
||||
{formatters.formatList(["A", "B", "C"])}|
|
||||
{formatters.formatRelativeTime(-1, "day", { numeric: "auto" })}
|
||||
</output>
|
||||
)
|
||||
}
|
||||
|
||||
describe("i18n runtime", () => {
|
||||
it("provides locale information and reactive translation helpers", () => {
|
||||
render(
|
||||
@@ -46,7 +83,7 @@ describe("i18n runtime", () => {
|
||||
catalogs={catalogs}
|
||||
locale="zh-Hans"
|
||||
locales={[
|
||||
{ label: "English", locale: "en" },
|
||||
{ label: "English", locale: "en-US" },
|
||||
{ label: "简体中文", locale: "zh-Hans" },
|
||||
]}
|
||||
>
|
||||
@@ -54,7 +91,7 @@ describe("i18n runtime", () => {
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByText("zh-Hans|en,zh-Hans|你好|你好")).toBeTruthy()
|
||||
expect(screen.getByText("zh-Hans|en-US,zh-Hans|你好|你好")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("merges package catalogs and lets application catalogs override them", () => {
|
||||
@@ -87,7 +124,7 @@ describe("i18n runtime", () => {
|
||||
|
||||
it("wraps Lingui Trans without adding a DOM element", () => {
|
||||
const { container } = render(
|
||||
<I18nProvider catalogs={catalogs} locale="en">
|
||||
<I18nProvider catalogs={catalogs} locale="en-US">
|
||||
<p>
|
||||
<Translate id="greeting" message="Fallback" />
|
||||
</p>
|
||||
@@ -110,10 +147,6 @@ describe("i18n runtime", () => {
|
||||
})
|
||||
|
||||
it("infers right-to-left locale direction", () => {
|
||||
function DirectionProbe() {
|
||||
return <span>{useLocales()[0]?.direction}</span>
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider locale="ar" locales={["ar"]}>
|
||||
<DirectionProbe />
|
||||
@@ -124,36 +157,16 @@ describe("i18n runtime", () => {
|
||||
})
|
||||
|
||||
it("uses localized language names when locale labels are omitted", () => {
|
||||
function LocaleLabelsProbe() {
|
||||
return (
|
||||
<span>
|
||||
{useLocales()
|
||||
.map((locale) => locale.label)
|
||||
.join("|")}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider locale="en" locales={["en", "zh-Hans"]}>
|
||||
<I18nProvider locale="en-US" locales={["en-US", "zh-Hans"]}>
|
||||
<LocaleLabelsProbe />
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByText("English|简体中文")).toBeTruthy()
|
||||
expect(screen.getByText("American English|简体中文")).toBeTruthy()
|
||||
})
|
||||
|
||||
it("keeps catalog locales separate from Intl language tags", () => {
|
||||
function LocaleProbe() {
|
||||
const locale = useLocaleDefinition()
|
||||
|
||||
return (
|
||||
<span>
|
||||
{locale.locale}|{locale.languageTag}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider
|
||||
locale="zh-Hans"
|
||||
@@ -167,23 +180,10 @@ describe("i18n runtime", () => {
|
||||
})
|
||||
|
||||
it("formats standard internationalized values through Intl", () => {
|
||||
function FormatterProbe() {
|
||||
const formatters = useFormatters()
|
||||
|
||||
return (
|
||||
<output>
|
||||
{formatters.formatNumber(1234.5)}|
|
||||
{formatters.formatCurrency(1234.5, "USD")}|
|
||||
{formatters.formatList(["A", "B", "C"])}|
|
||||
{formatters.formatRelativeTime(-1, "day", { numeric: "auto" })}
|
||||
</output>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider
|
||||
locale="en"
|
||||
locales={[{ languageTag: "en-US", locale: "en" }]}
|
||||
locale="en-US"
|
||||
locales={[{ languageTag: "en-US", locale: "en-US" }]}
|
||||
>
|
||||
<FormatterProbe />
|
||||
</I18nProvider>
|
||||
|
||||
@@ -139,6 +139,8 @@ ${createCatalogLoaderRuntime(`(async () => {
|
||||
function replaceMessageIds(code: string, schema: MessageIdSchema) {
|
||||
let transformedCode = code
|
||||
|
||||
// ES2022 compatibility: Object.entries returns a fresh array.
|
||||
// oxlint-disable-next-line unicorn/no-array-sort
|
||||
for (const [id, compactId] of Object.entries(schema.messages).sort(
|
||||
([left], [right]) => right.length - left.length
|
||||
)) {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { lexicalMessages } from "../messages"
|
||||
import { messages as en } from "./en"
|
||||
import { messages as enUS } from "./en-US"
|
||||
import { messages as zhHans } from "./zh-Hans"
|
||||
|
||||
describe("Lexical locale catalogs", () => {
|
||||
it("ships every built-in message in every locale", () => {
|
||||
const messageIds = Object.values(lexicalMessages)
|
||||
.map((descriptor) => descriptor.id)
|
||||
.sort()
|
||||
const messageIds = Object.values(lexicalMessages).map(
|
||||
(descriptor) => descriptor.id
|
||||
)
|
||||
messageIds.sort()
|
||||
|
||||
for (const messages of [en, zhHans]) {
|
||||
for (const messages of [enUS, zhHans]) {
|
||||
// Object.keys returns a fresh array.
|
||||
// oxlint-disable-next-line unicorn/no-array-sort
|
||||
expect(Object.keys(messages).sort()).toEqual(messageIds)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { lexicalMessages } from "../messages"
|
||||
|
||||
export const lexicalCatalogLocales = ["en", "zh-Hans"] as const
|
||||
export const lexicalCatalogLocales = ["en-US", "zh-Hans"] as const
|
||||
|
||||
type LexicalMessageId =
|
||||
(typeof lexicalMessages)[keyof typeof lexicalMessages]["id"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LexicalMessageCatalog } from "./catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
|
||||
export const messages = {
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
findLexicalActions,
|
||||
} from "./action-declarations"
|
||||
import { LexicalActionsProvider } from "./actions-context"
|
||||
import { messages as englishMessages } from "./locales/en"
|
||||
import { messages as englishMessages } from "./locales/en-US"
|
||||
|
||||
const pluginKeys = new WeakMap<ComponentType, string>()
|
||||
let nextPluginKey = 0
|
||||
@@ -46,7 +46,7 @@ function LexicalI18nBoundary({ children }: { children: ReactNode }) {
|
||||
if (hasI18nProvider) return children
|
||||
|
||||
return (
|
||||
<I18nProvider catalogs={{ en: englishMessages }} locale="en">
|
||||
<I18nProvider catalogs={{ "en-US": englishMessages }} locale="en-US">
|
||||
{children}
|
||||
</I18nProvider>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
export const uiCatalogLocales = [
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fr",
|
||||
"ja",
|
||||
"ko",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const
|
||||
export const uiCatalogLocales = ["en-US", "zh-Hans"] as const
|
||||
|
||||
export const uiMessageIds = [
|
||||
"ui.breadcrumb.label",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export { de as calendarLocale } from "react-day-picker/locale/de"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "de"
|
||||
export const languageTag = "de-DE"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "Breadcrumb-Navigation",
|
||||
"ui.carousel.nextSlide": "Nächste Folie",
|
||||
"ui.carousel.previousSlide": "Vorherige Folie",
|
||||
"ui.common.close": "Schließen",
|
||||
"ui.common.loading": "Wird geladen",
|
||||
"ui.common.more": "Mehr",
|
||||
"ui.pagination.label": "Seitennavigation",
|
||||
"ui.pagination.more": "Weitere Seiten",
|
||||
"ui.pagination.next": "Weiter",
|
||||
"ui.pagination.nextPage": "Zur nächsten Seite",
|
||||
"ui.pagination.previous": "Zurück",
|
||||
"ui.pagination.previousPage": "Zur vorherigen Seite",
|
||||
"ui.sidebar.description": "Zeigt die mobile Seitenleiste an.",
|
||||
"ui.sidebar.title": "Seitenleiste",
|
||||
"ui.sidebar.toggle": "Seitenleiste umschalten",
|
||||
"ui.toast.close": "Benachrichtigung schließen",
|
||||
} as const satisfies UiMessageCatalog
|
||||
@@ -2,7 +2,7 @@ export { enUS as calendarLocale } from "react-day-picker/locale/en-US"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "en"
|
||||
export const locale = "en-US"
|
||||
export const languageTag = "en-US"
|
||||
|
||||
export const messages = {
|
||||
@@ -1,25 +0,0 @@
|
||||
export { es as calendarLocale } from "react-day-picker/locale/es"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "es"
|
||||
export const languageTag = "es-ES"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "Ruta de navegación",
|
||||
"ui.carousel.nextSlide": "Diapositiva siguiente",
|
||||
"ui.carousel.previousSlide": "Diapositiva anterior",
|
||||
"ui.common.close": "Cerrar",
|
||||
"ui.common.loading": "Cargando",
|
||||
"ui.common.more": "Más",
|
||||
"ui.pagination.label": "Paginación",
|
||||
"ui.pagination.more": "Más páginas",
|
||||
"ui.pagination.next": "Siguiente",
|
||||
"ui.pagination.nextPage": "Ir a la página siguiente",
|
||||
"ui.pagination.previous": "Anterior",
|
||||
"ui.pagination.previousPage": "Ir a la página anterior",
|
||||
"ui.sidebar.description": "Muestra la barra lateral móvil.",
|
||||
"ui.sidebar.title": "Barra lateral",
|
||||
"ui.sidebar.toggle": "Alternar barra lateral",
|
||||
"ui.toast.close": "Cerrar notificación",
|
||||
} as const satisfies UiMessageCatalog
|
||||
@@ -1,25 +0,0 @@
|
||||
export { fr as calendarLocale } from "react-day-picker/locale/fr"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "fr"
|
||||
export const languageTag = "fr-FR"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "Fil d’Ariane",
|
||||
"ui.carousel.nextSlide": "Diapositive suivante",
|
||||
"ui.carousel.previousSlide": "Diapositive précédente",
|
||||
"ui.common.close": "Fermer",
|
||||
"ui.common.loading": "Chargement",
|
||||
"ui.common.more": "Plus",
|
||||
"ui.pagination.label": "Pagination",
|
||||
"ui.pagination.more": "Plus de pages",
|
||||
"ui.pagination.next": "Suivant",
|
||||
"ui.pagination.nextPage": "Aller à la page suivante",
|
||||
"ui.pagination.previous": "Précédent",
|
||||
"ui.pagination.previousPage": "Aller à la page précédente",
|
||||
"ui.sidebar.description": "Affiche la barre latérale mobile.",
|
||||
"ui.sidebar.title": "Barre latérale",
|
||||
"ui.sidebar.toggle": "Afficher ou masquer la barre latérale",
|
||||
"ui.toast.close": "Fermer la notification",
|
||||
} as const satisfies UiMessageCatalog
|
||||
@@ -1,25 +0,0 @@
|
||||
export { ja as calendarLocale } from "react-day-picker/locale/ja"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "ja"
|
||||
export const languageTag = "ja-JP"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "パンくずリスト",
|
||||
"ui.carousel.nextSlide": "次のスライド",
|
||||
"ui.carousel.previousSlide": "前のスライド",
|
||||
"ui.common.close": "閉じる",
|
||||
"ui.common.loading": "読み込み中",
|
||||
"ui.common.more": "その他",
|
||||
"ui.pagination.label": "ページネーション",
|
||||
"ui.pagination.more": "その他のページ",
|
||||
"ui.pagination.next": "次へ",
|
||||
"ui.pagination.nextPage": "次のページへ移動",
|
||||
"ui.pagination.previous": "前へ",
|
||||
"ui.pagination.previousPage": "前のページへ移動",
|
||||
"ui.sidebar.description": "モバイルサイドバーを表示します。",
|
||||
"ui.sidebar.title": "サイドバー",
|
||||
"ui.sidebar.toggle": "サイドバーを切り替える",
|
||||
"ui.toast.close": "通知を閉じる",
|
||||
} as const satisfies UiMessageCatalog
|
||||
@@ -1,25 +0,0 @@
|
||||
export { ko as calendarLocale } from "react-day-picker/locale/ko"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "ko"
|
||||
export const languageTag = "ko-KR"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "이동 경로",
|
||||
"ui.carousel.nextSlide": "다음 슬라이드",
|
||||
"ui.carousel.previousSlide": "이전 슬라이드",
|
||||
"ui.common.close": "닫기",
|
||||
"ui.common.loading": "불러오는 중",
|
||||
"ui.common.more": "더 보기",
|
||||
"ui.pagination.label": "페이지 탐색",
|
||||
"ui.pagination.more": "더 많은 페이지",
|
||||
"ui.pagination.next": "다음",
|
||||
"ui.pagination.nextPage": "다음 페이지로 이동",
|
||||
"ui.pagination.previous": "이전",
|
||||
"ui.pagination.previousPage": "이전 페이지로 이동",
|
||||
"ui.sidebar.description": "모바일 사이드바를 표시합니다.",
|
||||
"ui.sidebar.title": "사이드바",
|
||||
"ui.sidebar.toggle": "사이드바 전환",
|
||||
"ui.toast.close": "알림 닫기",
|
||||
} as const satisfies UiMessageCatalog
|
||||
@@ -1,25 +0,0 @@
|
||||
export { zhTW as calendarLocale } from "react-day-picker/locale/zh-TW"
|
||||
|
||||
import type { UiMessageCatalog } from "../catalogs"
|
||||
|
||||
export const locale = "zh-Hant"
|
||||
export const languageTag = "zh-TW"
|
||||
|
||||
export const messages = {
|
||||
"ui.breadcrumb.label": "麵包屑導覽",
|
||||
"ui.carousel.nextSlide": "下一張",
|
||||
"ui.carousel.previousSlide": "上一張",
|
||||
"ui.common.close": "關閉",
|
||||
"ui.common.loading": "載入中",
|
||||
"ui.common.more": "更多",
|
||||
"ui.pagination.label": "分頁導覽",
|
||||
"ui.pagination.more": "更多頁面",
|
||||
"ui.pagination.next": "下一頁",
|
||||
"ui.pagination.nextPage": "前往下一頁",
|
||||
"ui.pagination.previous": "上一頁",
|
||||
"ui.pagination.previousPage": "前往上一頁",
|
||||
"ui.sidebar.description": "顯示行動版側欄。",
|
||||
"ui.sidebar.title": "側欄",
|
||||
"ui.sidebar.toggle": "切換側欄",
|
||||
"ui.toast.close": "關閉通知",
|
||||
} as const satisfies UiMessageCatalog
|
||||
Reference in New Issue
Block a user