@@ -29,15 +37,23 @@ export function ThemeConfigPanel({
) : (
)}
- {scheme === "light" ? "浅色主题" : "深色主题"}
- {active && 使用中}
+
+
+
+ {active && (
+
+
+
+ )}
-
{description}
+
+
+
}
value={config.baseColor}
onChange={(baseColor) =>
setConfig((current) => ({ ...current, baseColor }))
@@ -45,7 +61,7 @@ export function ThemeConfigPanel({
/>
}
value={config.accentColor}
onChange={(accentColor) =>
setConfig((current) => ({ ...current, accentColor }))
diff --git a/packages/blocks/src/blocks/appearance/theme.test.ts b/packages/blocks/src/blocks/appearance/theme.test.ts
index c1c5223..33c6c26 100644
--- a/packages/blocks/src/blocks/appearance/theme.test.ts
+++ b/packages/blocks/src/blocks/appearance/theme.test.ts
@@ -14,6 +14,7 @@ describe("appearance themes", () => {
expect(themes.map((theme) => theme.name)).toEqual(selectableColors)
expect(new Set(selectableColors).size).toBe(selectableColors.length)
+ expect(new Set(themes.map((theme) => theme.name)).size).toBe(themes.length)
expect(themes.every((theme) => theme.title.length > 0)).toBe(true)
})
diff --git a/packages/blocks/src/blocks/appearance/themes.ts b/packages/blocks/src/blocks/appearance/themes.ts
index 7f8c6fd..32260fd 100644
--- a/packages/blocks/src/blocks/appearance/themes.ts
+++ b/packages/blocks/src/blocks/appearance/themes.ts
@@ -69,7 +69,7 @@ export type Theme = {
export const themes: Theme[] = [
{
name: "neutral",
- title: "中性灰",
+ title: "Neutral",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -142,7 +142,7 @@ export const themes: Theme[] = [
},
{
name: "stone",
- title: "石灰",
+ title: "Stone",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -215,7 +215,7 @@ export const themes: Theme[] = [
},
{
name: "zinc",
- title: "锌灰",
+ title: "Zinc",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -288,7 +288,7 @@ export const themes: Theme[] = [
},
{
name: "mauve",
- title: "紫灰",
+ title: "Mauve",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -361,7 +361,7 @@ export const themes: Theme[] = [
},
{
name: "olive",
- title: "橄榄灰",
+ title: "Olive",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -434,7 +434,7 @@ export const themes: Theme[] = [
},
{
name: "mist",
- title: "雾灰",
+ title: "Mist",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -507,7 +507,7 @@ export const themes: Theme[] = [
},
{
name: "taupe",
- title: "灰褐",
+ title: "Taupe",
cssVars: {
light: {
background: "oklch(1 0 0)",
@@ -580,7 +580,7 @@ export const themes: Theme[] = [
},
{
name: "amber",
- title: "琥珀色",
+ title: "Amber",
cssVars: {
light: {
primary: "oklch(0.555 0.163 48.998)",
@@ -612,7 +612,7 @@ export const themes: Theme[] = [
},
{
name: "blue",
- title: "蓝色",
+ title: "Blue",
cssVars: {
light: {
primary: "oklch(0.488 0.243 264.376)",
@@ -644,7 +644,7 @@ export const themes: Theme[] = [
},
{
name: "cyan",
- title: "青色",
+ title: "Cyan",
cssVars: {
light: {
primary: "oklch(0.52 0.105 223.128)",
@@ -676,7 +676,7 @@ export const themes: Theme[] = [
},
{
name: "emerald",
- title: "翠绿色",
+ title: "Emerald",
cssVars: {
light: {
primary: "oklch(0.508 0.118 165.612)",
@@ -708,7 +708,7 @@ export const themes: Theme[] = [
},
{
name: "fuchsia",
- title: "紫红色",
+ title: "Fuchsia",
cssVars: {
light: {
primary: "oklch(0.518 0.253 323.949)",
@@ -740,7 +740,7 @@ export const themes: Theme[] = [
},
{
name: "green",
- title: "绿色",
+ title: "Green",
cssVars: {
light: {
primary: "oklch(0.527 0.154 150.069)",
@@ -772,7 +772,7 @@ export const themes: Theme[] = [
},
{
name: "indigo",
- title: "靛蓝色",
+ title: "Indigo",
cssVars: {
light: {
primary: "oklch(0.457 0.24 277.023)",
@@ -804,7 +804,7 @@ export const themes: Theme[] = [
},
{
name: "lime",
- title: "青柠色",
+ title: "Lime",
cssVars: {
light: {
primary: "oklch(0.841 0.238 128.85)",
@@ -836,7 +836,7 @@ export const themes: Theme[] = [
},
{
name: "orange",
- title: "橙色",
+ title: "Orange",
cssVars: {
light: {
primary: "oklch(0.553 0.195 38.402)",
@@ -868,7 +868,7 @@ export const themes: Theme[] = [
},
{
name: "pink",
- title: "粉色",
+ title: "Pink",
cssVars: {
light: {
primary: "oklch(0.525 0.223 3.958)",
@@ -900,7 +900,7 @@ export const themes: Theme[] = [
},
{
name: "purple",
- title: "紫色",
+ title: "Purple",
cssVars: {
light: {
primary: "oklch(0.496 0.265 301.924)",
@@ -932,7 +932,7 @@ export const themes: Theme[] = [
},
{
name: "red",
- title: "红色",
+ title: "Red",
cssVars: {
light: {
primary: "oklch(0.505 0.213 27.518)",
@@ -964,7 +964,7 @@ export const themes: Theme[] = [
},
{
name: "rose",
- title: "玫红色",
+ title: "Rose",
cssVars: {
light: {
primary: "oklch(0.514 0.222 16.935)",
@@ -997,7 +997,7 @@ export const themes: Theme[] = [
},
{
name: "sky",
- title: "天蓝色",
+ title: "Sky",
cssVars: {
light: {
primary: "oklch(0.5 0.134 242.749)",
@@ -1029,7 +1029,7 @@ export const themes: Theme[] = [
},
{
name: "teal",
- title: "蓝绿色",
+ title: "Teal",
cssVars: {
light: {
primary: "oklch(0.511 0.096 186.391)",
@@ -1061,7 +1061,7 @@ export const themes: Theme[] = [
},
{
name: "violet",
- title: "紫罗兰色",
+ title: "Violet",
cssVars: {
light: {
primary: "oklch(0.491 0.27 292.581)",
@@ -1093,7 +1093,7 @@ export const themes: Theme[] = [
},
{
name: "yellow",
- title: "黄色",
+ title: "Yellow",
cssVars: {
light: {
primary: "oklch(0.852 0.199 91.936)",
diff --git a/packages/blocks/src/blocks/chats/chat-popover.tsx b/packages/blocks/src/blocks/chats/chat-popover.tsx
index 0908fd4..54a0e28 100644
--- a/packages/blocks/src/blocks/chats/chat-popover.tsx
+++ b/packages/blocks/src/blocks/chats/chat-popover.tsx
@@ -1,3 +1,4 @@
+import { useMessage } from "@workspace/i18n"
import { ItemGroup } from "@workspace/ui/components/item"
import {
Popover,
@@ -8,6 +9,7 @@ import {
} from "@workspace/ui/components/popover"
import { useOnScroll } from "../../hooks/use-on-scroll"
+import { chatMessages } from "./messages"
import { chatPopoverHandle } from "./chat-popover-handle"
import { ChatThreadItem } from "./chat-thread-item"
@@ -18,7 +20,9 @@ export interface ChatPopoverProps {
title?: string
}
-export function ChatPopover({ threads, title = "Chats" }: ChatPopoverProps) {
+export function ChatPopover({ threads, title }: ChatPopoverProps) {
+ const defaultTitle = useMessage(chatMessages.title)
+
useOnScroll(() => {
chatPopoverHandle.close()
})
@@ -28,7 +32,7 @@ export function ChatPopover({ threads, title = "Chats" }: ChatPopoverProps) {
- {title}({threads.length})
+ {title ?? defaultTitle}({threads.length})
diff --git a/packages/blocks/src/blocks/chats/locales/catalogs.test.ts b/packages/blocks/src/blocks/chats/locales/catalogs.test.ts
new file mode 100644
index 0000000..57875b9
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/catalogs.test.ts
@@ -0,0 +1,34 @@
+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 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,
+ "zh-Hans": zhHans,
+ "zh-Hant": zhHant,
+ },
+ locales: chatCatalogLocales,
+ messageIds: Object.values(chatMessages).map(
+ (descriptor) => descriptor.id
+ ),
+ })
+ })
+})
diff --git a/packages/blocks/src/blocks/chats/locales/catalogs.ts b/packages/blocks/src/blocks/chats/locales/catalogs.ts
new file mode 100644
index 0000000..d84a72c
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/catalogs.ts
@@ -0,0 +1,10 @@
+import type { chatMessages } from "../messages"
+import {
+ blockCatalogLocales,
+ type BlockCatalogLocale,
+ type BlockMessageCatalog,
+} from "../../../i18n/catalogs"
+
+export { blockCatalogLocales as chatCatalogLocales }
+export type ChatCatalogLocale = BlockCatalogLocale
+export type ChatMessageCatalog = BlockMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/de.ts b/packages/blocks/src/blocks/chats/locales/de.ts
new file mode 100644
index 0000000..31f8b6b
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/de.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "de"
+export const languageTag = "de-DE"
+export const messages = {
+ "blocks.chats.title": "Chats",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/en.ts b/packages/blocks/src/blocks/chats/locales/en.ts
new file mode 100644
index 0000000..03142b0
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/en.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "en"
+export const languageTag = "en-US"
+export const messages = {
+ "blocks.chats.title": "Chats",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/es.ts b/packages/blocks/src/blocks/chats/locales/es.ts
new file mode 100644
index 0000000..3344b3d
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/es.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "es"
+export const languageTag = "es-ES"
+export const messages = {
+ "blocks.chats.title": "Chats",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/fr.ts b/packages/blocks/src/blocks/chats/locales/fr.ts
new file mode 100644
index 0000000..517f63f
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/fr.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "fr"
+export const languageTag = "fr-FR"
+export const messages = {
+ "blocks.chats.title": "Discussions",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/ja.ts b/packages/blocks/src/blocks/chats/locales/ja.ts
new file mode 100644
index 0000000..ee8295e
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/ja.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "ja"
+export const languageTag = "ja-JP"
+export const messages = {
+ "blocks.chats.title": "チャット",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/ko.ts b/packages/blocks/src/blocks/chats/locales/ko.ts
new file mode 100644
index 0000000..6b03c4b
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/ko.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "ko"
+export const languageTag = "ko-KR"
+export const messages = {
+ "blocks.chats.title": "채팅",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/zh-Hans.ts b/packages/blocks/src/blocks/chats/locales/zh-Hans.ts
new file mode 100644
index 0000000..fc2d3db
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/zh-Hans.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "zh-Hans"
+export const languageTag = "zh-CN"
+export const messages = {
+ "blocks.chats.title": "聊天",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/locales/zh-Hant.ts b/packages/blocks/src/blocks/chats/locales/zh-Hant.ts
new file mode 100644
index 0000000..2529f3c
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/locales/zh-Hant.ts
@@ -0,0 +1,7 @@
+import type { ChatMessageCatalog } from "./catalogs"
+
+export const locale = "zh-Hant"
+export const languageTag = "zh-TW"
+export const messages = {
+ "blocks.chats.title": "聊天",
+} as const satisfies ChatMessageCatalog
diff --git a/packages/blocks/src/blocks/chats/messages.ts b/packages/blocks/src/blocks/chats/messages.ts
new file mode 100644
index 0000000..3987c60
--- /dev/null
+++ b/packages/blocks/src/blocks/chats/messages.ts
@@ -0,0 +1,8 @@
+import type { MessageDescriptor } from "@workspace/i18n"
+
+export const chatMessages = {
+ title: /* i18n */ {
+ id: "blocks.chats.title",
+ message: "Chats",
+ },
+} as const satisfies Record
diff --git a/packages/blocks/src/blocks/layout/app-breadcrumb.tsx b/packages/blocks/src/blocks/layout/app-breadcrumb.tsx
index 5fd2b3f..502e43a 100644
--- a/packages/blocks/src/blocks/layout/app-breadcrumb.tsx
+++ b/packages/blocks/src/blocks/layout/app-breadcrumb.tsx
@@ -1,5 +1,6 @@
import { Link } from "@tanstack/react-router"
import * as React from "react"
+import { useMessage } from "@workspace/i18n"
import {
Breadcrumb,
BreadcrumbEllipsis,
@@ -21,8 +22,13 @@ import {
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
-import { type NavigationInfo, useNavigationStack } from "../navigation"
+import {
+ NavigationLabel,
+ type NavigationInfo,
+ useNavigationStack,
+} from "../navigation"
import { Icon } from "../../components/icon"
+import { layoutMessages } from "./messages"
const MAX_VISIBLE_ENTRIES = 3
@@ -185,7 +191,7 @@ function BreadcrumbEntryContent({ entry }: { entry: BreadcrumbEntry }) {
return (
<>
{entry.icon && }
- {entry.label}
+
>
)
}
@@ -303,11 +309,13 @@ function BreadcrumbRouteLink({
}
function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
+ const menuLabel = useMessage(layoutMessages.breadcrumbMenuLabel)
+
return (
+
}
>
@@ -320,7 +328,7 @@ function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
{entry.icon && (
)}
- {entry.label}
+
>
)
@@ -405,7 +413,9 @@ export function AppBreadcrumb() {
className="size-4 shrink-0"
/>
)}
- {item.entry.label}
+
+
+
) : (
)}
- {entry.label}
+
))}
diff --git a/packages/blocks/src/blocks/layout/app-layout.tsx b/packages/blocks/src/blocks/layout/app-layout.tsx
index 52d4efc..aa57063 100644
--- a/packages/blocks/src/blocks/layout/app-layout.tsx
+++ b/packages/blocks/src/blocks/layout/app-layout.tsx
@@ -38,6 +38,7 @@ import { AppQueryIndicator } from "./app-query-indicator"
export interface AppLayoutProps {
chatThreads: readonly ChatThread[]
children: React.ReactNode
+ headerActions?: React.ReactNode
navigationGroups: readonly NavigationGroup[]
notifications: UseNotificationsOptions
}
@@ -45,6 +46,7 @@ export interface AppLayoutProps {
export function AppLayout({
chatThreads,
children,
+ headerActions,
navigationGroups,
notifications,
}: AppLayoutProps) {
@@ -55,7 +57,7 @@ export function AppLayout({
-
+
@@ -66,7 +68,7 @@ export function AppLayout({
)
}
-function AppHeader() {
+function AppHeader({ actions }: { actions?: React.ReactNode }) {
return (
@@ -157,6 +159,7 @@ function AppHeader() {
+ {actions}
}
/>
diff --git a/packages/blocks/src/blocks/layout/app-query-indicator.tsx b/packages/blocks/src/blocks/layout/app-query-indicator.tsx
index 12fab6d..a6a7aba 100644
--- a/packages/blocks/src/blocks/layout/app-query-indicator.tsx
+++ b/packages/blocks/src/blocks/layout/app-query-indicator.tsx
@@ -1,3 +1,4 @@
+import { useMessage } from "@workspace/i18n"
import { useRefresh } from "../../hooks/use-refresh"
import {
Tooltip,
@@ -7,9 +8,12 @@ import {
import { HeaderActionButton } from "./header-action-button"
import { ReloadIcon } from "@hugeicons/core-free-icons"
import { cn } from "@workspace/ui/lib/utils"
+import { layoutMessages } from "./messages"
export function AppQueryIndicator({ className }: { className?: string }) {
const { canRefresh, isRefreshing, refresh } = useRefresh()
+ const refreshLabel = useMessage(layoutMessages.refreshLabel)
+ const refreshDescription = useMessage(layoutMessages.refreshDescription)
if (isRefreshing) {
return (
@@ -29,7 +33,7 @@ export function AppQueryIndicator({ className }: { className?: string }) {
}
/>
-
- 点击可以刷新
-
- 当前页面数据
-
+
{refreshDescription}
)
}
diff --git a/packages/blocks/src/blocks/layout/locales/catalogs.test.ts b/packages/blocks/src/blocks/layout/locales/catalogs.test.ts
new file mode 100644
index 0000000..ecadf8b
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/catalogs.test.ts
@@ -0,0 +1,34 @@
+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 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,
+ "zh-Hans": zhHans,
+ "zh-Hant": zhHant,
+ },
+ locales: layoutCatalogLocales,
+ messageIds: Object.values(layoutMessages).map(
+ (descriptor) => descriptor.id
+ ),
+ })
+ })
+})
diff --git a/packages/blocks/src/blocks/layout/locales/catalogs.ts b/packages/blocks/src/blocks/layout/locales/catalogs.ts
new file mode 100644
index 0000000..61d6702
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/catalogs.ts
@@ -0,0 +1,10 @@
+import type { layoutMessages } from "../messages"
+import {
+ blockCatalogLocales,
+ type BlockCatalogLocale,
+ type BlockMessageCatalog,
+} from "../../../i18n/catalogs"
+
+export { blockCatalogLocales as layoutCatalogLocales }
+export type LayoutCatalogLocale = BlockCatalogLocale
+export type LayoutMessageCatalog = BlockMessageCatalog
diff --git a/packages/blocks/src/blocks/layout/locales/de.ts b/packages/blocks/src/blocks/layout/locales/de.ts
new file mode 100644
index 0000000..da0e82b
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/de.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/locales/en.ts b/packages/blocks/src/blocks/layout/locales/en.ts
new file mode 100644
index 0000000..c2dac5e
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/en.ts
@@ -0,0 +1,15 @@
+import type { LayoutMessageCatalog } from "./catalogs"
+
+export const locale = "en"
+export const languageTag = "en-US"
+export const messages = {
+ "blocks.breadcrumb.menuLabel": "Open breadcrumb menu",
+ "blocks.refresh.description": "Refresh the current page data",
+ "blocks.refresh.label": "Refresh current page data",
+ "blocks.userMenu.accountPassword": "Account password",
+ "blocks.userMenu.appearance": "Appearance",
+ "blocks.userMenu.keybindings": "Keybindings",
+ "blocks.userMenu.logout": "Log out",
+ "blocks.userMenu.open": "Open user menu",
+ "blocks.userMenu.title": "User menu",
+} as const satisfies LayoutMessageCatalog
diff --git a/packages/blocks/src/blocks/layout/locales/es.ts b/packages/blocks/src/blocks/layout/locales/es.ts
new file mode 100644
index 0000000..7730366
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/es.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/locales/fr.ts b/packages/blocks/src/blocks/layout/locales/fr.ts
new file mode 100644
index 0000000..4a7a1c7
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/fr.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/locales/ja.ts b/packages/blocks/src/blocks/layout/locales/ja.ts
new file mode 100644
index 0000000..4af6f0a
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/ja.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/locales/ko.ts b/packages/blocks/src/blocks/layout/locales/ko.ts
new file mode 100644
index 0000000..379e69b
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/ko.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/locales/zh-Hans.ts b/packages/blocks/src/blocks/layout/locales/zh-Hans.ts
new file mode 100644
index 0000000..c45632a
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/zh-Hans.ts
@@ -0,0 +1,15 @@
+import type { LayoutMessageCatalog } from "./catalogs"
+
+export const locale = "zh-Hans"
+export const languageTag = "zh-CN"
+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
diff --git a/packages/blocks/src/blocks/layout/locales/zh-Hant.ts b/packages/blocks/src/blocks/layout/locales/zh-Hant.ts
new file mode 100644
index 0000000..840b910
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/locales/zh-Hant.ts
@@ -0,0 +1,15 @@
+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
diff --git a/packages/blocks/src/blocks/layout/messages.ts b/packages/blocks/src/blocks/layout/messages.ts
new file mode 100644
index 0000000..82f4fe8
--- /dev/null
+++ b/packages/blocks/src/blocks/layout/messages.ts
@@ -0,0 +1,40 @@
+import type { MessageDescriptor } from "@workspace/i18n"
+
+export const layoutMessages = {
+ accountPassword: /* i18n */ {
+ id: "blocks.userMenu.accountPassword",
+ message: "Account password",
+ },
+ appearance: /* i18n */ {
+ id: "blocks.userMenu.appearance",
+ message: "Appearance",
+ },
+ breadcrumbMenuLabel: /* i18n */ {
+ id: "blocks.breadcrumb.menuLabel",
+ message: "Open breadcrumb menu",
+ },
+ keybindings: /* i18n */ {
+ id: "blocks.userMenu.keybindings",
+ message: "Keybindings",
+ },
+ logout: /* i18n */ {
+ id: "blocks.userMenu.logout",
+ message: "Log out",
+ },
+ openUserMenu: /* i18n */ {
+ id: "blocks.userMenu.open",
+ message: "Open user menu",
+ },
+ refreshDescription: /* i18n */ {
+ id: "blocks.refresh.description",
+ message: "Refresh the current page data",
+ },
+ refreshLabel: /* i18n */ {
+ id: "blocks.refresh.label",
+ message: "Refresh current page data",
+ },
+ userMenuTitle: /* i18n */ {
+ id: "blocks.userMenu.title",
+ message: "User menu",
+ },
+} as const satisfies Record
diff --git a/packages/blocks/src/blocks/layout/user-menu.tsx b/packages/blocks/src/blocks/layout/user-menu.tsx
index 787ca4b..4a09438 100644
--- a/packages/blocks/src/blocks/layout/user-menu.tsx
+++ b/packages/blocks/src/blocks/layout/user-menu.tsx
@@ -1,4 +1,5 @@
import { formatForDisplay } from "@tanstack/react-hotkeys"
+import { useMessage } from "@workspace/i18n"
import { Button, type ButtonProps } from "@workspace/ui/components/button"
import {
createDialogHandle,
@@ -41,8 +42,10 @@ import {
ShieldKeyIcon,
SwatchIcon,
} from "@hugeicons/core-free-icons"
+import { layoutMessages } from "./messages"
interface UserMenuItem {
+ id: string
action?: NavigationAction
shortcut?: string
icon: IconData
@@ -53,24 +56,6 @@ interface UserMenuTriggerProps {
className?: string
}
-const userMenuItems: readonly UserMenuItem[] = [
- {
- action: navigationActions.appearance,
- icon: SwatchIcon,
- label: "界面外观",
- shortcut: "Mod+,",
- },
- {
- icon: KeyboardIcon,
- label: "按键映射",
- },
- {
- icon: ShieldKeyIcon,
- label: "账号密码",
- shortcut: "Mod+P",
- },
-]
-
let userMenuHandles:
| {
dialog: DialogHandle
@@ -123,8 +108,11 @@ function UserMenuPopoverTrigger({ className }: UserMenuTriggerProps) {
}
function UserMenuTriggerButton({ className, ...props }: ButtonProps) {
+ const openUserMenuLabel = useMessage(layoutMessages.openUserMenu)
+
return (
@@ -153,7 +166,7 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
{userMenuItems.map((item) => (
- -
+
-
@@ -213,12 +226,14 @@ function UserMenuDialogTrigger({ className }: UserMenuTriggerProps) {
function UserMenuDialog() {
const { dialog } = getOrCreateUserMenuHandles()
+ const title = useMessage(layoutMessages.userMenuTitle)
+
return (