Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8cf3a1f03a | |||
| 6af4d7b798 | |||
| ae1798f6f4 | |||
| 0768e8fa29 | |||
| 0d0aad1d47 |
@@ -19,6 +19,9 @@ dist-ssr
|
|||||||
# turbo
|
# turbo
|
||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
|
# generated i18n build schema
|
||||||
|
.i18n
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
"sourceLocale": "en",
|
"sourceLocale": "en",
|
||||||
"locales": ["en", "zh-Hans"],
|
"locales": ["en", "zh-Hans"],
|
||||||
"catalogPath": "src/locales/{locale}/messages",
|
"catalogPath": "src/locales/{locale}/messages",
|
||||||
|
"catalogSources": [
|
||||||
|
"@workspace/ui/locales/{locale}",
|
||||||
|
"@workspace/blocks/appearance/locales/{locale}",
|
||||||
|
"@workspace/blocks/chats/locales/{locale}",
|
||||||
|
"@workspace/blocks/layout/locales/{locale}",
|
||||||
|
"@workspace/blocks/navigation/locales/{locale}",
|
||||||
|
"@workspace/blocks/notifications/locales/{locale}"
|
||||||
|
],
|
||||||
"include": ["src", "../../packages/blocks/src", "../../packages/ui/src"],
|
"include": ["src", "../../packages/blocks/src", "../../packages/ui/src"],
|
||||||
"exclude": ["**/*.test.{ts,tsx}", "**/node_modules/**"]
|
"exclude": ["**/*.test.{ts,tsx}", "**/node_modules/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import project from "./i18n.config.json" with { type: "json" }
|
|
||||||
import { createLinguiConfig } from "@workspace/i18n/lingui-config"
|
|
||||||
|
|
||||||
export default createLinguiConfig(project)
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"i18n:compile": "workspace-i18n compile",
|
"i18n:compile": "workspace-i18n compile",
|
||||||
"i18n:extract": "workspace-i18n extract",
|
"i18n:extract": "workspace-i18n extract",
|
||||||
"i18n:ui": "workspace-i18n ui",
|
"i18n:ui": "workspace-i18n ui",
|
||||||
|
"test": "vitest run",
|
||||||
"typecheck": "tsc -b",
|
"typecheck": "tsc -b",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"start": "node .output/server/index.mjs"
|
"start": "node .output/server/index.mjs"
|
||||||
@@ -31,8 +32,10 @@
|
|||||||
"@workspace/blocks": "workspace:*",
|
"@workspace/blocks": "workspace:*",
|
||||||
"@workspace/i18n": "workspace:*",
|
"@workspace/i18n": "workspace:*",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
|
"country-flag-icons": "^1.6.20",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
|
"react-day-picker": "^10.0.1",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"sonner": "^2.0.7"
|
"sonner": "^2.0.7"
|
||||||
},
|
},
|
||||||
@@ -43,6 +46,7 @@
|
|||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@vitejs/plugin-react": "^6",
|
"@vitejs/plugin-react": "^6",
|
||||||
|
"jsdom": "^30.0.1",
|
||||||
"nitro": "^3.0.260610-beta",
|
"nitro": "^3.0.260610-beta",
|
||||||
"typescript": "~6",
|
"typescript": "~6",
|
||||||
"vite": "^8",
|
"vite": "^8",
|
||||||
|
|||||||
@@ -0,0 +1,336 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
|
export const navigationMessages = {
|
||||||
|
overview: /* i18n */ {
|
||||||
|
id: "app.navigation.overview",
|
||||||
|
message: "Overview",
|
||||||
|
},
|
||||||
|
"overview-dashboard": /* i18n */ {
|
||||||
|
id: "app.navigation.overview-dashboard",
|
||||||
|
message: "Business overview",
|
||||||
|
},
|
||||||
|
"overview-realtime": /* i18n */ {
|
||||||
|
id: "app.navigation.overview-realtime",
|
||||||
|
message: "Real-time monitoring",
|
||||||
|
},
|
||||||
|
"overview-tasks": /* i18n */ {
|
||||||
|
id: "app.navigation.overview-tasks",
|
||||||
|
message: "Tasks",
|
||||||
|
},
|
||||||
|
"catalog-and-inventory": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-and-inventory",
|
||||||
|
message: "Products and inventory",
|
||||||
|
},
|
||||||
|
"catalog-products": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-products",
|
||||||
|
message: "Products",
|
||||||
|
},
|
||||||
|
"catalog-products-list": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-products-list",
|
||||||
|
message: "Product list",
|
||||||
|
},
|
||||||
|
"catalog-products-drafts": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-products-drafts",
|
||||||
|
message: "Product drafts",
|
||||||
|
},
|
||||||
|
"catalog-products-bundles": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-products-bundles",
|
||||||
|
message: "Product bundles",
|
||||||
|
},
|
||||||
|
"catalog-products-attributes": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-products-attributes",
|
||||||
|
message: "Product attributes",
|
||||||
|
},
|
||||||
|
"catalog-product-specifications": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-product-specifications",
|
||||||
|
message: "Specification templates",
|
||||||
|
},
|
||||||
|
"catalog-product-brands": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-product-brands",
|
||||||
|
message: "Brands",
|
||||||
|
},
|
||||||
|
"catalog-categories": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-categories",
|
||||||
|
message: "Categories",
|
||||||
|
},
|
||||||
|
"catalog-category-tree": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-category-tree",
|
||||||
|
message: "Category tree",
|
||||||
|
},
|
||||||
|
"catalog-category-navigation": /* i18n */ {
|
||||||
|
id: "app.navigation.catalog-category-navigation",
|
||||||
|
message: "Navigation categories",
|
||||||
|
},
|
||||||
|
"inventory-center": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-center",
|
||||||
|
message: "Inventory",
|
||||||
|
},
|
||||||
|
"inventory-overview": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-overview",
|
||||||
|
message: "Inventory overview",
|
||||||
|
},
|
||||||
|
"inventory-inbound": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-inbound",
|
||||||
|
message: "Inbound orders",
|
||||||
|
},
|
||||||
|
"inventory-outbound": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-outbound",
|
||||||
|
message: "Outbound orders",
|
||||||
|
},
|
||||||
|
"inventory-transfers": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-transfers",
|
||||||
|
message: "Stock transfers",
|
||||||
|
},
|
||||||
|
"inventory-alerts": /* i18n */ {
|
||||||
|
id: "app.navigation.inventory-alerts",
|
||||||
|
message: "Inventory alerts",
|
||||||
|
},
|
||||||
|
"content-assets": /* i18n */ {
|
||||||
|
id: "app.navigation.content-assets",
|
||||||
|
message: "Content",
|
||||||
|
},
|
||||||
|
"content-articles": /* i18n */ {
|
||||||
|
id: "app.navigation.content-articles",
|
||||||
|
message: "Articles",
|
||||||
|
},
|
||||||
|
"content-media": /* i18n */ {
|
||||||
|
id: "app.navigation.content-media",
|
||||||
|
message: "Media",
|
||||||
|
},
|
||||||
|
"content-pages": /* i18n */ {
|
||||||
|
id: "app.navigation.content-pages",
|
||||||
|
message: "Custom pages",
|
||||||
|
},
|
||||||
|
"trade-and-fulfillment": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-and-fulfillment",
|
||||||
|
message: "Orders and fulfillment",
|
||||||
|
},
|
||||||
|
"trade-orders": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders",
|
||||||
|
message: "Orders",
|
||||||
|
},
|
||||||
|
"trade-orders-awaiting-payment": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders-awaiting-payment",
|
||||||
|
message: "Awaiting payment",
|
||||||
|
},
|
||||||
|
"trade-orders-awaiting-shipment": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders-awaiting-shipment",
|
||||||
|
message: "Awaiting shipment",
|
||||||
|
},
|
||||||
|
"trade-orders-shipped": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders-shipped",
|
||||||
|
message: "In transit",
|
||||||
|
},
|
||||||
|
"trade-orders-completed": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders-completed",
|
||||||
|
message: "Completed orders",
|
||||||
|
},
|
||||||
|
"trade-orders-canceled": /* i18n */ {
|
||||||
|
id: "app.navigation.trade-orders-canceled",
|
||||||
|
message: "Canceled orders",
|
||||||
|
},
|
||||||
|
"fulfillment-center": /* i18n */ {
|
||||||
|
id: "app.navigation.fulfillment-center",
|
||||||
|
message: "Fulfillment",
|
||||||
|
},
|
||||||
|
"fulfillment-deliveries": /* i18n */ {
|
||||||
|
id: "app.navigation.fulfillment-deliveries",
|
||||||
|
message: "Delivery tasks",
|
||||||
|
},
|
||||||
|
"fulfillment-pickups": /* i18n */ {
|
||||||
|
id: "app.navigation.fulfillment-pickups",
|
||||||
|
message: "Pickup verification",
|
||||||
|
},
|
||||||
|
"fulfillment-freight-rules": /* i18n */ {
|
||||||
|
id: "app.navigation.fulfillment-freight-rules",
|
||||||
|
message: "Shipping rules",
|
||||||
|
},
|
||||||
|
"after-sales-center": /* i18n */ {
|
||||||
|
id: "app.navigation.after-sales-center",
|
||||||
|
message: "After-sales",
|
||||||
|
},
|
||||||
|
"after-sales-returns": /* i18n */ {
|
||||||
|
id: "app.navigation.after-sales-returns",
|
||||||
|
message: "Return requests",
|
||||||
|
},
|
||||||
|
"after-sales-refunds": /* i18n */ {
|
||||||
|
id: "app.navigation.after-sales-refunds",
|
||||||
|
message: "Refund review",
|
||||||
|
},
|
||||||
|
"after-sales-disputes": /* i18n */ {
|
||||||
|
id: "app.navigation.after-sales-disputes",
|
||||||
|
message: "Transaction disputes",
|
||||||
|
},
|
||||||
|
"customers-and-marketing": /* i18n */ {
|
||||||
|
id: "app.navigation.customers-and-marketing",
|
||||||
|
message: "Customers and marketing",
|
||||||
|
},
|
||||||
|
"customer-center": /* i18n */ {
|
||||||
|
id: "app.navigation.customer-center",
|
||||||
|
message: "Customers",
|
||||||
|
},
|
||||||
|
"customer-profiles": /* i18n */ {
|
||||||
|
id: "app.navigation.customer-profiles",
|
||||||
|
message: "Customer profiles",
|
||||||
|
},
|
||||||
|
"customer-segments": /* i18n */ {
|
||||||
|
id: "app.navigation.customer-segments",
|
||||||
|
message: "Customer segments",
|
||||||
|
},
|
||||||
|
"customer-tiers": /* i18n */ {
|
||||||
|
id: "app.navigation.customer-tiers",
|
||||||
|
message: "Customer tiers",
|
||||||
|
},
|
||||||
|
"customer-risk-list": /* i18n */ {
|
||||||
|
id: "app.navigation.customer-risk-list",
|
||||||
|
message: "Risk list",
|
||||||
|
},
|
||||||
|
"marketing-campaigns": /* i18n */ {
|
||||||
|
id: "app.navigation.marketing-campaigns",
|
||||||
|
message: "Marketing campaigns",
|
||||||
|
},
|
||||||
|
"marketing-coupons": /* i18n */ {
|
||||||
|
id: "app.navigation.marketing-coupons",
|
||||||
|
message: "Coupons",
|
||||||
|
},
|
||||||
|
"marketing-flash-sales": /* i18n */ {
|
||||||
|
id: "app.navigation.marketing-flash-sales",
|
||||||
|
message: "Flash sales",
|
||||||
|
},
|
||||||
|
"marketing-gift-promotions": /* i18n */ {
|
||||||
|
id: "app.navigation.marketing-gift-promotions",
|
||||||
|
message: "Gift promotions",
|
||||||
|
},
|
||||||
|
"marketing-referrals": /* i18n */ {
|
||||||
|
id: "app.navigation.marketing-referrals",
|
||||||
|
message: "Referral rewards",
|
||||||
|
},
|
||||||
|
"loyalty-center": /* i18n */ {
|
||||||
|
id: "app.navigation.loyalty-center",
|
||||||
|
message: "Membership benefits",
|
||||||
|
},
|
||||||
|
"loyalty-points": /* i18n */ {
|
||||||
|
id: "app.navigation.loyalty-points",
|
||||||
|
message: "Points rules",
|
||||||
|
},
|
||||||
|
"loyalty-stored-value": /* i18n */ {
|
||||||
|
id: "app.navigation.loyalty-stored-value",
|
||||||
|
message: "Stored-value accounts",
|
||||||
|
},
|
||||||
|
"loyalty-benefits": /* i18n */ {
|
||||||
|
id: "app.navigation.loyalty-benefits",
|
||||||
|
message: "Benefits configuration",
|
||||||
|
},
|
||||||
|
"data-and-finance": /* i18n */ {
|
||||||
|
id: "app.navigation.data-and-finance",
|
||||||
|
message: "Data and finance",
|
||||||
|
},
|
||||||
|
"report-center": /* i18n */ {
|
||||||
|
id: "app.navigation.report-center",
|
||||||
|
message: "Reports",
|
||||||
|
},
|
||||||
|
"report-sales": /* i18n */ {
|
||||||
|
id: "app.navigation.report-sales",
|
||||||
|
message: "Sales analysis",
|
||||||
|
},
|
||||||
|
"report-products": /* i18n */ {
|
||||||
|
id: "app.navigation.report-products",
|
||||||
|
message: "Product analysis",
|
||||||
|
},
|
||||||
|
"report-customers": /* i18n */ {
|
||||||
|
id: "app.navigation.report-customers",
|
||||||
|
message: "Customer analysis",
|
||||||
|
},
|
||||||
|
"report-channels": /* i18n */ {
|
||||||
|
id: "app.navigation.report-channels",
|
||||||
|
message: "Channel analysis",
|
||||||
|
},
|
||||||
|
"finance-center": /* i18n */ {
|
||||||
|
id: "app.navigation.finance-center",
|
||||||
|
message: "Finance",
|
||||||
|
},
|
||||||
|
"finance-settlements": /* i18n */ {
|
||||||
|
id: "app.navigation.finance-settlements",
|
||||||
|
message: "Settlement statements",
|
||||||
|
},
|
||||||
|
"finance-reconciliation": /* i18n */ {
|
||||||
|
id: "app.navigation.finance-reconciliation",
|
||||||
|
message: "Reconciliation",
|
||||||
|
},
|
||||||
|
"finance-invoices": /* i18n */ {
|
||||||
|
id: "app.navigation.finance-invoices",
|
||||||
|
message: "Invoices",
|
||||||
|
},
|
||||||
|
"finance-payouts": /* i18n */ {
|
||||||
|
id: "app.navigation.finance-payouts",
|
||||||
|
message: "Payout history",
|
||||||
|
},
|
||||||
|
"system-management": /* i18n */ {
|
||||||
|
id: "app.navigation.system-management",
|
||||||
|
message: "System management",
|
||||||
|
},
|
||||||
|
"system-members": /* i18n */ {
|
||||||
|
id: "app.navigation.system-members",
|
||||||
|
message: "Organization members",
|
||||||
|
},
|
||||||
|
"system-members-admins": /* i18n */ {
|
||||||
|
id: "app.navigation.system-members-admins",
|
||||||
|
message: "Administrator accounts",
|
||||||
|
},
|
||||||
|
"system-members-roles": /* i18n */ {
|
||||||
|
id: "app.navigation.system-members-roles",
|
||||||
|
message: "Roles",
|
||||||
|
},
|
||||||
|
"system-members-permissions": /* i18n */ {
|
||||||
|
id: "app.navigation.system-members-permissions",
|
||||||
|
message: "Permission policies",
|
||||||
|
},
|
||||||
|
"system-notifications": /* i18n */ {
|
||||||
|
id: "app.navigation.system-notifications",
|
||||||
|
message: "Notifications",
|
||||||
|
},
|
||||||
|
"system-notification-templates": /* i18n */ {
|
||||||
|
id: "app.navigation.system-notification-templates",
|
||||||
|
message: "Notification templates",
|
||||||
|
},
|
||||||
|
"system-notification-logs": /* i18n */ {
|
||||||
|
id: "app.navigation.system-notification-logs",
|
||||||
|
message: "Delivery logs",
|
||||||
|
},
|
||||||
|
"system-audit": /* i18n */ {
|
||||||
|
id: "app.navigation.system-audit",
|
||||||
|
message: "Audit log",
|
||||||
|
},
|
||||||
|
"system-integrations": /* i18n */ {
|
||||||
|
id: "app.navigation.system-integrations",
|
||||||
|
message: "Integrations",
|
||||||
|
},
|
||||||
|
"system-integration-api-keys": /* i18n */ {
|
||||||
|
id: "app.navigation.system-integration-api-keys",
|
||||||
|
message: "API keys",
|
||||||
|
},
|
||||||
|
"system-integration-webhooks": /* i18n */ {
|
||||||
|
id: "app.navigation.system-integration-webhooks",
|
||||||
|
message: "Webhook configuration",
|
||||||
|
},
|
||||||
|
"system-settings": /* i18n */ {
|
||||||
|
id: "app.navigation.system-settings",
|
||||||
|
message: "System settings",
|
||||||
|
},
|
||||||
|
"system-settings-store": /* i18n */ {
|
||||||
|
id: "app.navigation.system-settings-store",
|
||||||
|
message: "Store information",
|
||||||
|
},
|
||||||
|
"system-settings-payments": /* i18n */ {
|
||||||
|
id: "app.navigation.system-settings-payments",
|
||||||
|
message: "Payment channels",
|
||||||
|
},
|
||||||
|
"system-settings-shipping": /* i18n */ {
|
||||||
|
id: "app.navigation.system-settings-shipping",
|
||||||
|
message: "Shipping settings",
|
||||||
|
},
|
||||||
|
"system-recycle-bin": /* i18n */ {
|
||||||
|
id: "app.navigation.system-recycle-bin",
|
||||||
|
message: "Recycle bin",
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, MessageDescriptor>
|
||||||
@@ -60,73 +60,75 @@ import {
|
|||||||
|
|
||||||
import type { NavigationGroup } from "@workspace/blocks/navigation"
|
import type { NavigationGroup } from "@workspace/blocks/navigation"
|
||||||
|
|
||||||
|
import { navigationMessages } from "./navigation-messages"
|
||||||
|
|
||||||
export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
||||||
{
|
{
|
||||||
id: "overview",
|
id: "overview",
|
||||||
label: "概览",
|
label: navigationMessages["overview"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "overview-dashboard",
|
id: "overview-dashboard",
|
||||||
to: "/",
|
to: "/",
|
||||||
label: "经营总览",
|
label: navigationMessages["overview-dashboard"],
|
||||||
icon: Analytics01Icon,
|
icon: Analytics01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "overview-realtime",
|
id: "overview-realtime",
|
||||||
to: "/analytics/realtime",
|
to: "/analytics/realtime",
|
||||||
label: "实时监控",
|
label: navigationMessages["overview-realtime"],
|
||||||
icon: AnalyticsUpIcon,
|
icon: AnalyticsUpIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "overview-tasks",
|
id: "overview-tasks",
|
||||||
to: "/workspace/tasks",
|
to: "/workspace/tasks",
|
||||||
label: "待办中心",
|
label: navigationMessages["overview-tasks"],
|
||||||
icon: Task01Icon,
|
icon: Task01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-and-inventory",
|
id: "catalog-and-inventory",
|
||||||
label: "商品与库存",
|
label: navigationMessages["catalog-and-inventory"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "catalog-products",
|
id: "catalog-products",
|
||||||
label: "商品中心",
|
label: navigationMessages["catalog-products"],
|
||||||
icon: ShoppingBag01Icon,
|
icon: ShoppingBag01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "catalog-products-list",
|
id: "catalog-products-list",
|
||||||
to: "/catalog/products/list",
|
to: "/catalog/products/list",
|
||||||
label: "商品列表",
|
label: navigationMessages["catalog-products-list"],
|
||||||
icon: ShoppingBag01Icon,
|
icon: ShoppingBag01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-products-drafts",
|
id: "catalog-products-drafts",
|
||||||
to: "/catalog/products/drafts",
|
to: "/catalog/products/drafts",
|
||||||
label: "商品草稿",
|
label: navigationMessages["catalog-products-drafts"],
|
||||||
icon: File01Icon,
|
icon: File01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-products-bundles",
|
id: "catalog-products-bundles",
|
||||||
to: "/catalog/products/bundles",
|
to: "/catalog/products/bundles",
|
||||||
label: "组合商品",
|
label: navigationMessages["catalog-products-bundles"],
|
||||||
icon: Package02Icon,
|
icon: Package02Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-products-attributes",
|
id: "catalog-products-attributes",
|
||||||
label: "商品属性",
|
label: navigationMessages["catalog-products-attributes"],
|
||||||
icon: Settings03Icon,
|
icon: Settings03Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "catalog-product-specifications",
|
id: "catalog-product-specifications",
|
||||||
to: "/catalog/products/attributes/specifications",
|
to: "/catalog/products/attributes/specifications",
|
||||||
label: "规格模板",
|
label: navigationMessages["catalog-product-specifications"],
|
||||||
icon: ClipboardListIcon,
|
icon: ClipboardListIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-product-brands",
|
id: "catalog-product-brands",
|
||||||
to: "/catalog/products/attributes/brands",
|
to: "/catalog/products/attributes/brands",
|
||||||
label: "品牌资料",
|
label: navigationMessages["catalog-product-brands"],
|
||||||
icon: StarIcon,
|
icon: StarIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -136,19 +138,19 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "catalog-categories",
|
id: "catalog-categories",
|
||||||
to: "/catalog/categories",
|
to: "/catalog/categories",
|
||||||
label: "分类目录",
|
label: navigationMessages["catalog-categories"],
|
||||||
icon: ClipboardListIcon,
|
icon: ClipboardListIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "catalog-category-tree",
|
id: "catalog-category-tree",
|
||||||
to: "/catalog/categories/tree",
|
to: "/catalog/categories/tree",
|
||||||
label: "分类树",
|
label: navigationMessages["catalog-category-tree"],
|
||||||
icon: Structure01Icon,
|
icon: Structure01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "catalog-category-navigation",
|
id: "catalog-category-navigation",
|
||||||
to: "/catalog/categories/navigation",
|
to: "/catalog/categories/navigation",
|
||||||
label: "导航分类",
|
label: navigationMessages["catalog-category-navigation"],
|
||||||
icon: Compass01Icon,
|
icon: Compass01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -156,37 +158,37 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "inventory-center",
|
id: "inventory-center",
|
||||||
to: "/inventory",
|
to: "/inventory",
|
||||||
label: "库存中心",
|
label: navigationMessages["inventory-center"],
|
||||||
icon: PackageIcon,
|
icon: PackageIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "inventory-overview",
|
id: "inventory-overview",
|
||||||
to: "/inventory/overview",
|
to: "/inventory/overview",
|
||||||
label: "库存总览",
|
label: navigationMessages["inventory-overview"],
|
||||||
icon: Chart01Icon,
|
icon: Chart01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "inventory-inbound",
|
id: "inventory-inbound",
|
||||||
to: "/inventory/inbound",
|
to: "/inventory/inbound",
|
||||||
label: "入库单",
|
label: navigationMessages["inventory-inbound"],
|
||||||
icon: InboxDownloadIcon,
|
icon: InboxDownloadIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "inventory-outbound",
|
id: "inventory-outbound",
|
||||||
to: "/inventory/outbound",
|
to: "/inventory/outbound",
|
||||||
label: "出库单",
|
label: navigationMessages["inventory-outbound"],
|
||||||
icon: InboxUploadIcon,
|
icon: InboxUploadIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "inventory-transfers",
|
id: "inventory-transfers",
|
||||||
to: "/inventory/transfers",
|
to: "/inventory/transfers",
|
||||||
label: "调拨单",
|
label: navigationMessages["inventory-transfers"],
|
||||||
icon: ArrowDataTransferHorizontalIcon,
|
icon: ArrowDataTransferHorizontalIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "inventory-alerts",
|
id: "inventory-alerts",
|
||||||
to: "/inventory/alerts",
|
to: "/inventory/alerts",
|
||||||
label: "库存预警",
|
label: navigationMessages["inventory-alerts"],
|
||||||
icon: Alert02Icon,
|
icon: Alert02Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -194,25 +196,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "content-assets",
|
id: "content-assets",
|
||||||
to: "/content",
|
to: "/content",
|
||||||
label: "内容素材",
|
label: navigationMessages["content-assets"],
|
||||||
icon: File01Icon,
|
icon: File01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "content-articles",
|
id: "content-articles",
|
||||||
to: "/content/articles",
|
to: "/content/articles",
|
||||||
label: "内容文章",
|
label: navigationMessages["content-articles"],
|
||||||
icon: File01Icon,
|
icon: File01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "content-media",
|
id: "content-media",
|
||||||
to: "/content/media",
|
to: "/content/media",
|
||||||
label: "媒体资源",
|
label: navigationMessages["content-media"],
|
||||||
icon: GalleryHorizontalIcon,
|
icon: GalleryHorizontalIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "content-pages",
|
id: "content-pages",
|
||||||
to: "/content/pages",
|
to: "/content/pages",
|
||||||
label: "自定义页面",
|
label: navigationMessages["content-pages"],
|
||||||
icon: AppWindowIcon,
|
icon: AppWindowIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -221,42 +223,42 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "trade-and-fulfillment",
|
id: "trade-and-fulfillment",
|
||||||
label: "交易与履约",
|
label: navigationMessages["trade-and-fulfillment"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "trade-orders",
|
id: "trade-orders",
|
||||||
to: "/trade/orders",
|
to: "/trade/orders",
|
||||||
label: "订单中心",
|
label: navigationMessages["trade-orders"],
|
||||||
icon: ShoppingCart01Icon,
|
icon: ShoppingCart01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "trade-orders-awaiting-payment",
|
id: "trade-orders-awaiting-payment",
|
||||||
to: "/trade/orders/awaiting-payment",
|
to: "/trade/orders/awaiting-payment",
|
||||||
label: "待付款订单",
|
label: navigationMessages["trade-orders-awaiting-payment"],
|
||||||
icon: WalletCardsIcon,
|
icon: WalletCardsIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "trade-orders-awaiting-shipment",
|
id: "trade-orders-awaiting-shipment",
|
||||||
to: "/trade/orders/awaiting-shipment",
|
to: "/trade/orders/awaiting-shipment",
|
||||||
label: "待发货订单",
|
label: navigationMessages["trade-orders-awaiting-shipment"],
|
||||||
icon: PackageIcon,
|
icon: PackageIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "trade-orders-shipped",
|
id: "trade-orders-shipped",
|
||||||
to: "/trade/orders/shipped",
|
to: "/trade/orders/shipped",
|
||||||
label: "配送中订单",
|
label: navigationMessages["trade-orders-shipped"],
|
||||||
icon: DeliveryTruck01Icon,
|
icon: DeliveryTruck01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "trade-orders-completed",
|
id: "trade-orders-completed",
|
||||||
to: "/trade/orders/completed",
|
to: "/trade/orders/completed",
|
||||||
label: "已完成订单",
|
label: navigationMessages["trade-orders-completed"],
|
||||||
icon: CheckmarkCircle01Icon,
|
icon: CheckmarkCircle01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "trade-orders-canceled",
|
id: "trade-orders-canceled",
|
||||||
to: "/trade/orders/canceled",
|
to: "/trade/orders/canceled",
|
||||||
label: "已取消订单",
|
label: navigationMessages["trade-orders-canceled"],
|
||||||
icon: CancelCircleIcon,
|
icon: CancelCircleIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -264,25 +266,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "fulfillment-center",
|
id: "fulfillment-center",
|
||||||
to: "/fulfillment",
|
to: "/fulfillment",
|
||||||
label: "配送履约",
|
label: navigationMessages["fulfillment-center"],
|
||||||
icon: DeliveryTruck01Icon,
|
icon: DeliveryTruck01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "fulfillment-deliveries",
|
id: "fulfillment-deliveries",
|
||||||
to: "/fulfillment/deliveries",
|
to: "/fulfillment/deliveries",
|
||||||
label: "配送任务",
|
label: navigationMessages["fulfillment-deliveries"],
|
||||||
icon: Location01Icon,
|
icon: Location01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fulfillment-pickups",
|
id: "fulfillment-pickups",
|
||||||
to: "/fulfillment/pickups",
|
to: "/fulfillment/pickups",
|
||||||
label: "自提核销",
|
label: navigationMessages["fulfillment-pickups"],
|
||||||
icon: Store01Icon,
|
icon: Store01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fulfillment-freight-rules",
|
id: "fulfillment-freight-rules",
|
||||||
to: "/fulfillment/freight-rules",
|
to: "/fulfillment/freight-rules",
|
||||||
label: "运费规则",
|
label: navigationMessages["fulfillment-freight-rules"],
|
||||||
icon: RoadLocation01Icon,
|
icon: RoadLocation01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -290,25 +292,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "after-sales-center",
|
id: "after-sales-center",
|
||||||
to: "/after-sales",
|
to: "/after-sales",
|
||||||
label: "售后中心",
|
label: navigationMessages["after-sales-center"],
|
||||||
icon: CallMissed01Icon,
|
icon: CallMissed01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "after-sales-returns",
|
id: "after-sales-returns",
|
||||||
to: "/after-sales/returns",
|
to: "/after-sales/returns",
|
||||||
label: "退货申请",
|
label: navigationMessages["after-sales-returns"],
|
||||||
icon: Refresh01Icon,
|
icon: Refresh01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "after-sales-refunds",
|
id: "after-sales-refunds",
|
||||||
to: "/after-sales/refunds",
|
to: "/after-sales/refunds",
|
||||||
label: "退款审核",
|
label: navigationMessages["after-sales-refunds"],
|
||||||
icon: MoneySendSquareIcon,
|
icon: MoneySendSquareIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "after-sales-disputes",
|
id: "after-sales-disputes",
|
||||||
to: "/after-sales/disputes",
|
to: "/after-sales/disputes",
|
||||||
label: "交易纠纷",
|
label: navigationMessages["after-sales-disputes"],
|
||||||
icon: SecurityWarningIcon,
|
icon: SecurityWarningIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -317,36 +319,36 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "customers-and-marketing",
|
id: "customers-and-marketing",
|
||||||
label: "客户与营销",
|
label: navigationMessages["customers-and-marketing"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "customer-center",
|
id: "customer-center",
|
||||||
to: "/customers",
|
to: "/customers",
|
||||||
label: "客户中心",
|
label: navigationMessages["customer-center"],
|
||||||
icon: IdentityCardIcon,
|
icon: IdentityCardIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "customer-profiles",
|
id: "customer-profiles",
|
||||||
to: "/customers/profiles",
|
to: "/customers/profiles",
|
||||||
label: "客户档案",
|
label: navigationMessages["customer-profiles"],
|
||||||
icon: IdentityCardIcon,
|
icon: IdentityCardIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "customer-segments",
|
id: "customer-segments",
|
||||||
to: "/customers/segments",
|
to: "/customers/segments",
|
||||||
label: "客户分群",
|
label: navigationMessages["customer-segments"],
|
||||||
icon: UserGroupIcon,
|
icon: UserGroupIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "customer-tiers",
|
id: "customer-tiers",
|
||||||
to: "/customers/tiers",
|
to: "/customers/tiers",
|
||||||
label: "客户等级",
|
label: navigationMessages["customer-tiers"],
|
||||||
icon: CrownIcon,
|
icon: CrownIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "customer-risk-list",
|
id: "customer-risk-list",
|
||||||
to: "/customers/risk-list",
|
to: "/customers/risk-list",
|
||||||
label: "风险名单",
|
label: navigationMessages["customer-risk-list"],
|
||||||
icon: SecurityWarningIcon,
|
icon: SecurityWarningIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -354,31 +356,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "marketing-campaigns",
|
id: "marketing-campaigns",
|
||||||
to: "/marketing/campaigns",
|
to: "/marketing/campaigns",
|
||||||
label: "营销活动",
|
label: navigationMessages["marketing-campaigns"],
|
||||||
icon: Discount01Icon,
|
icon: Discount01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "marketing-coupons",
|
id: "marketing-coupons",
|
||||||
to: "/marketing/coupons",
|
to: "/marketing/coupons",
|
||||||
label: "优惠券管理",
|
label: navigationMessages["marketing-coupons"],
|
||||||
icon: Ticket01Icon,
|
icon: Ticket01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "marketing-flash-sales",
|
id: "marketing-flash-sales",
|
||||||
to: "/marketing/flash-sales",
|
to: "/marketing/flash-sales",
|
||||||
label: "限时折扣",
|
label: navigationMessages["marketing-flash-sales"],
|
||||||
icon: Clock01Icon,
|
icon: Clock01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "marketing-gift-promotions",
|
id: "marketing-gift-promotions",
|
||||||
to: "/marketing/gift-promotions",
|
to: "/marketing/gift-promotions",
|
||||||
label: "满赠活动",
|
label: navigationMessages["marketing-gift-promotions"],
|
||||||
icon: GiftIcon,
|
icon: GiftIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "marketing-referrals",
|
id: "marketing-referrals",
|
||||||
to: "/marketing/referrals",
|
to: "/marketing/referrals",
|
||||||
label: "邀请有礼",
|
label: navigationMessages["marketing-referrals"],
|
||||||
icon: Share01Icon,
|
icon: Share01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -386,25 +388,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "loyalty-center",
|
id: "loyalty-center",
|
||||||
to: "/loyalty",
|
to: "/loyalty",
|
||||||
label: "会员权益",
|
label: navigationMessages["loyalty-center"],
|
||||||
icon: Medal01Icon,
|
icon: Medal01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "loyalty-points",
|
id: "loyalty-points",
|
||||||
to: "/loyalty/points",
|
to: "/loyalty/points",
|
||||||
label: "积分规则",
|
label: navigationMessages["loyalty-points"],
|
||||||
icon: StarCircleIcon,
|
icon: StarCircleIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "loyalty-stored-value",
|
id: "loyalty-stored-value",
|
||||||
to: "/loyalty/stored-value",
|
to: "/loyalty/stored-value",
|
||||||
label: "储值账户",
|
label: navigationMessages["loyalty-stored-value"],
|
||||||
icon: Wallet01Icon,
|
icon: Wallet01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "loyalty-benefits",
|
id: "loyalty-benefits",
|
||||||
to: "/loyalty/benefits",
|
to: "/loyalty/benefits",
|
||||||
label: "权益配置",
|
label: navigationMessages["loyalty-benefits"],
|
||||||
icon: CheckmarkBadge01Icon,
|
icon: CheckmarkBadge01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -413,36 +415,36 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "data-and-finance",
|
id: "data-and-finance",
|
||||||
label: "数据与财务",
|
label: navigationMessages["data-and-finance"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "report-center",
|
id: "report-center",
|
||||||
to: "/reports",
|
to: "/reports",
|
||||||
label: "数据报表",
|
label: navigationMessages["report-center"],
|
||||||
icon: Chart01Icon,
|
icon: Chart01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "report-sales",
|
id: "report-sales",
|
||||||
to: "/reports/sales",
|
to: "/reports/sales",
|
||||||
label: "销售分析",
|
label: navigationMessages["report-sales"],
|
||||||
icon: AnalyticsUpIcon,
|
icon: AnalyticsUpIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "report-products",
|
id: "report-products",
|
||||||
to: "/reports/products",
|
to: "/reports/products",
|
||||||
label: "商品分析",
|
label: navigationMessages["report-products"],
|
||||||
icon: ShoppingBag01Icon,
|
icon: ShoppingBag01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "report-customers",
|
id: "report-customers",
|
||||||
to: "/reports/customers",
|
to: "/reports/customers",
|
||||||
label: "客群分析",
|
label: navigationMessages["report-customers"],
|
||||||
icon: UserGroupIcon,
|
icon: UserGroupIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "report-channels",
|
id: "report-channels",
|
||||||
to: "/reports/channels",
|
to: "/reports/channels",
|
||||||
label: "渠道分析",
|
label: navigationMessages["report-channels"],
|
||||||
icon: ChartIncreaseIcon,
|
icon: ChartIncreaseIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -450,31 +452,31 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "finance-center",
|
id: "finance-center",
|
||||||
to: "/finance",
|
to: "/finance",
|
||||||
label: "财务中心",
|
label: navigationMessages["finance-center"],
|
||||||
icon: WalletCardsIcon,
|
icon: WalletCardsIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "finance-settlements",
|
id: "finance-settlements",
|
||||||
to: "/finance/settlements",
|
to: "/finance/settlements",
|
||||||
label: "结算账单",
|
label: navigationMessages["finance-settlements"],
|
||||||
icon: Invoice03Icon,
|
icon: Invoice03Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "finance-reconciliation",
|
id: "finance-reconciliation",
|
||||||
to: "/finance/reconciliation",
|
to: "/finance/reconciliation",
|
||||||
label: "对账管理",
|
label: navigationMessages["finance-reconciliation"],
|
||||||
icon: Calculator01Icon,
|
icon: Calculator01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "finance-invoices",
|
id: "finance-invoices",
|
||||||
to: "/finance/invoices",
|
to: "/finance/invoices",
|
||||||
label: "发票管理",
|
label: navigationMessages["finance-invoices"],
|
||||||
icon: File01Icon,
|
icon: File01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "finance-payouts",
|
id: "finance-payouts",
|
||||||
to: "/finance/payouts",
|
to: "/finance/payouts",
|
||||||
label: "打款记录",
|
label: navigationMessages["finance-payouts"],
|
||||||
icon: MoneySendSquareIcon,
|
icon: MoneySendSquareIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -483,30 +485,30 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-management",
|
id: "system-management",
|
||||||
label: "系统管理",
|
label: navigationMessages["system-management"],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "system-members",
|
id: "system-members",
|
||||||
to: "/system/members",
|
to: "/system/members",
|
||||||
label: "组织成员",
|
label: navigationMessages["system-members"],
|
||||||
icon: UserShield01Icon,
|
icon: UserShield01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "system-members-admins",
|
id: "system-members-admins",
|
||||||
to: "/system/members/admins",
|
to: "/system/members/admins",
|
||||||
label: "管理员账号",
|
label: navigationMessages["system-members-admins"],
|
||||||
icon: UserShield01Icon,
|
icon: UserShield01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-members-roles",
|
id: "system-members-roles",
|
||||||
to: "/system/members/roles",
|
to: "/system/members/roles",
|
||||||
label: "角色管理",
|
label: navigationMessages["system-members-roles"],
|
||||||
icon: UserGroupIcon,
|
icon: UserGroupIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-members-permissions",
|
id: "system-members-permissions",
|
||||||
to: "/system/members/permissions",
|
to: "/system/members/permissions",
|
||||||
label: "权限策略",
|
label: navigationMessages["system-members-permissions"],
|
||||||
icon: LockKeyIcon,
|
icon: LockKeyIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -514,19 +516,19 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "system-notifications",
|
id: "system-notifications",
|
||||||
to: "/system/notifications",
|
to: "/system/notifications",
|
||||||
label: "消息中心",
|
label: navigationMessages["system-notifications"],
|
||||||
icon: BubbleChatIcon,
|
icon: BubbleChatIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "system-notification-templates",
|
id: "system-notification-templates",
|
||||||
to: "/system/notifications/templates",
|
to: "/system/notifications/templates",
|
||||||
label: "通知模板",
|
label: navigationMessages["system-notification-templates"],
|
||||||
icon: File01Icon,
|
icon: File01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-notification-logs",
|
id: "system-notification-logs",
|
||||||
to: "/system/notifications/logs",
|
to: "/system/notifications/logs",
|
||||||
label: "发送记录",
|
label: navigationMessages["system-notification-logs"],
|
||||||
icon: HistoryIcon,
|
icon: HistoryIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -534,25 +536,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "system-audit",
|
id: "system-audit",
|
||||||
to: "/system/audit",
|
to: "/system/audit",
|
||||||
label: "操作审计",
|
label: navigationMessages["system-audit"],
|
||||||
icon: Note01Icon,
|
icon: Note01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-integrations",
|
id: "system-integrations",
|
||||||
to: "/system/integrations",
|
to: "/system/integrations",
|
||||||
label: "开放集成",
|
label: navigationMessages["system-integrations"],
|
||||||
icon: CodeSquareIcon,
|
icon: CodeSquareIcon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "system-integration-api-keys",
|
id: "system-integration-api-keys",
|
||||||
to: "/system/integrations/api-keys",
|
to: "/system/integrations/api-keys",
|
||||||
label: "API 密钥",
|
label: navigationMessages["system-integration-api-keys"],
|
||||||
icon: Key01Icon,
|
icon: Key01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-integration-webhooks",
|
id: "system-integration-webhooks",
|
||||||
to: "/system/integrations/webhooks",
|
to: "/system/integrations/webhooks",
|
||||||
label: "Webhook 配置",
|
label: navigationMessages["system-integration-webhooks"],
|
||||||
icon: LinkCircleIcon,
|
icon: LinkCircleIcon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -560,25 +562,25 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "system-settings",
|
id: "system-settings",
|
||||||
to: "/system-configs",
|
to: "/system-configs",
|
||||||
label: "系统配置",
|
label: navigationMessages["system-settings"],
|
||||||
icon: Settings01Icon,
|
icon: Settings01Icon,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: "system-settings-store",
|
id: "system-settings-store",
|
||||||
to: "/system-configs/store",
|
to: "/system-configs/store",
|
||||||
label: "门店资料",
|
label: navigationMessages["system-settings-store"],
|
||||||
icon: Store01Icon,
|
icon: Store01Icon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-settings-payments",
|
id: "system-settings-payments",
|
||||||
to: "/system-configs/payments",
|
to: "/system-configs/payments",
|
||||||
label: "支付渠道",
|
label: navigationMessages["system-settings-payments"],
|
||||||
icon: CreditCardIcon,
|
icon: CreditCardIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-settings-shipping",
|
id: "system-settings-shipping",
|
||||||
to: "/system-configs/shipping",
|
to: "/system-configs/shipping",
|
||||||
label: "配送设置",
|
label: navigationMessages["system-settings-shipping"],
|
||||||
icon: DeliveryTruck01Icon,
|
icon: DeliveryTruck01Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -586,7 +588,7 @@ export const primaryNavigationGroups: readonly NavigationGroup[] = [
|
|||||||
{
|
{
|
||||||
id: "system-recycle-bin",
|
id: "system-recycle-bin",
|
||||||
to: "/system/recycle-bin",
|
to: "/system/recycle-bin",
|
||||||
label: "回收站",
|
label: navigationMessages["system-recycle-bin"],
|
||||||
icon: Delete02Icon,
|
icon: Delete02Icon,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { lazy, Suspense } from "react"
|
||||||
|
|
||||||
|
const LazyI18nDevtool = import.meta.env.DEV
|
||||||
|
? lazy(() =>
|
||||||
|
import("@workspace/i18n/devtool").then((module) => ({
|
||||||
|
default: module.I18nDevtool,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
|
||||||
|
export function AppI18nDevtool() {
|
||||||
|
if (!LazyI18nDevtool) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<LazyI18nDevtool dark={"ui\\:dark"} />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,51 +1,105 @@
|
|||||||
import { lazy, Suspense, type PropsWithChildren } from "react"
|
import {
|
||||||
import { I18nProvider, type LocaleDefinition } from "@workspace/i18n"
|
createContext,
|
||||||
|
startTransition,
|
||||||
|
use,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
type PropsWithChildren,
|
||||||
|
} from "react"
|
||||||
|
import {
|
||||||
|
I18nProvider,
|
||||||
|
useLocaleDefinition,
|
||||||
|
useTranslate,
|
||||||
|
} from "@workspace/i18n"
|
||||||
|
import { loadMessageCatalog } from "@workspace/i18n/catalogs"
|
||||||
|
import {
|
||||||
|
I18nProvider as UiI18nProvider,
|
||||||
|
type CalendarLocale,
|
||||||
|
} from "@workspace/ui/components/i18n"
|
||||||
|
import { calendarLocale as englishCalendarLocale } from "@workspace/ui/locales/en"
|
||||||
|
import { calendarLocale as simplifiedChineseCalendarLocale } from "@workspace/ui/locales/zh-Hans"
|
||||||
|
|
||||||
import { messages as englishMessages } from "@/locales/en/messages"
|
import { appLocales, DEFAULT_APP_LOCALE, type AppLocale } from "./app-locales"
|
||||||
import { messages as simplifiedChineseMessages } from "@/locales/zh-Hans/messages"
|
import { AppI18nDevtool } from "./app-i18n-devtool"
|
||||||
|
import { setAppLocale as persistAppLocale } from "@/server/app-locale.functions"
|
||||||
|
|
||||||
const I18nDevtool = import.meta.env.DEV
|
const appCalendarLocales: Partial<Record<AppLocale, CalendarLocale>> = {
|
||||||
? lazy(() =>
|
en: englishCalendarLocale,
|
||||||
import("@workspace/i18n/devtool").then((module) => ({
|
"zh-Hans": simplifiedChineseCalendarLocale,
|
||||||
default: module.I18nDevtool,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
|
|
||||||
export const appLocales = [
|
|
||||||
{
|
|
||||||
label: "English",
|
|
||||||
locale: "en",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "简体中文",
|
|
||||||
locale: "zh-Hans",
|
|
||||||
},
|
|
||||||
] as const satisfies readonly LocaleDefinition[]
|
|
||||||
|
|
||||||
export type AppLocale = (typeof appLocales)[number]["locale"]
|
|
||||||
|
|
||||||
const appCatalogs = {
|
|
||||||
en: englishMessages,
|
|
||||||
"zh-Hans": simplifiedChineseMessages,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppI18nProviderProps extends PropsWithChildren {
|
export interface AppI18nProviderProps extends PropsWithChildren {
|
||||||
locale?: AppLocale
|
locale?: AppLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AppLocaleContextValue {
|
||||||
|
locale: AppLocale
|
||||||
|
setLocale: (locale: AppLocale) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppLocaleContext = createContext<AppLocaleContextValue | null>(null)
|
||||||
|
|
||||||
export function AppI18nProvider({
|
export function AppI18nProvider({
|
||||||
children,
|
children,
|
||||||
locale = "zh-Hans",
|
locale: initialLocale = DEFAULT_APP_LOCALE,
|
||||||
}: AppI18nProviderProps) {
|
}: AppI18nProviderProps) {
|
||||||
|
const [locale, setLocaleState] = useState(initialLocale)
|
||||||
|
const messages = use(loadMessageCatalog(locale))
|
||||||
|
const catalogs = useMemo(() => ({ [locale]: messages }), [locale, messages])
|
||||||
|
const setLocale = useCallback((nextLocale: AppLocale) => {
|
||||||
|
startTransition(() => {
|
||||||
|
setLocaleState(nextLocale)
|
||||||
|
})
|
||||||
|
void persistAppLocale({ data: nextLocale })
|
||||||
|
}, [])
|
||||||
|
const localeContext = useMemo(
|
||||||
|
() => ({ locale, setLocale }),
|
||||||
|
[locale, setLocale]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const definition = appLocales.find(
|
||||||
|
(candidate) => candidate.locale === locale
|
||||||
|
)
|
||||||
|
|
||||||
|
document.documentElement.lang = definition?.languageTag ?? locale
|
||||||
|
document.documentElement.dir = definition?.direction ?? "ltr"
|
||||||
|
}, [locale])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nProvider catalogs={appCatalogs} locale={locale} locales={appLocales}>
|
<AppLocaleContext.Provider value={localeContext}>
|
||||||
{children}
|
<I18nProvider catalogs={catalogs} locale={locale} locales={appLocales}>
|
||||||
{I18nDevtool && (
|
<AppUiI18nProvider>{children}</AppUiI18nProvider>
|
||||||
<Suspense fallback={null}>
|
<AppI18nDevtool />
|
||||||
<I18nDevtool dark={"ui\\:dark"} />
|
</I18nProvider>
|
||||||
</Suspense>
|
</AppLocaleContext.Provider>
|
||||||
)}
|
)
|
||||||
</I18nProvider>
|
}
|
||||||
|
|
||||||
|
export function useAppLocale() {
|
||||||
|
const context = useContext(AppLocaleContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAppLocale must be used within AppI18nProvider")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppUiI18nProvider({ children }: PropsWithChildren) {
|
||||||
|
const locale = useLocaleDefinition()
|
||||||
|
const translate = useTranslate()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UiI18nProvider
|
||||||
|
calendarLocale={appCalendarLocales[locale.locale as AppLocale]}
|
||||||
|
locale={locale.languageTag ?? locale.locale}
|
||||||
|
t={translate}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</UiI18nProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react"
|
||||||
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"
|
||||||
|
|
||||||
|
import { AppLanguageSwitcher } from "./app-language-switcher"
|
||||||
|
|
||||||
|
vi.mock("./app-i18n-provider", () => ({
|
||||||
|
useAppLocale: () => ({
|
||||||
|
locale: "en",
|
||||||
|
setLocale: vi.fn(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock("country-flag-icons/react/1x1", () => ({
|
||||||
|
CN: () => <svg />,
|
||||||
|
US: () => <svg />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
vi.stubGlobal(
|
||||||
|
"ResizeObserver",
|
||||||
|
class {
|
||||||
|
disconnect() {}
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(cleanup)
|
||||||
|
|
||||||
|
describe("AppLanguageSwitcher", () => {
|
||||||
|
it("opens its grouped locale choices", async () => {
|
||||||
|
render(<AppLanguageSwitcher />)
|
||||||
|
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getByRole("button", {
|
||||||
|
name: "Change language, currently English",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(await screen.findByText("简体中文")).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { CN, US } from "country-flag-icons/react/1x1"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@workspace/ui/components/dropdown-menu"
|
||||||
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
import { HeaderActionButton } from "@workspace/blocks/layout"
|
||||||
|
|
||||||
|
import { appLocales, isAppLocale, type AppLocaleCountry } from "./app-locales"
|
||||||
|
import { useAppLocale } from "./app-i18n-provider"
|
||||||
|
|
||||||
|
const localeFlags = {
|
||||||
|
CN,
|
||||||
|
US,
|
||||||
|
} satisfies Record<AppLocaleCountry, typeof CN>
|
||||||
|
|
||||||
|
function LocaleFlag({
|
||||||
|
country,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
className?: string
|
||||||
|
country: AppLocaleCountry
|
||||||
|
}) {
|
||||||
|
const Flag = localeFlags[country]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn(
|
||||||
|
"inline-flex shrink-0 overflow-hidden rounded-full ring-1 ring-foreground/15",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Flag className="size-full" />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppLanguageSwitcher() {
|
||||||
|
const { locale, setLocale } = useAppLocale()
|
||||||
|
const activeLocale =
|
||||||
|
appLocales.find((definition) => definition.locale === locale) ??
|
||||||
|
appLocales[0]
|
||||||
|
const menuLabel = locale === "zh-Hans" ? "语言" : "Language"
|
||||||
|
const triggerLabel =
|
||||||
|
locale === "zh-Hans"
|
||||||
|
? `切换语言,当前为${activeLocale.label}`
|
||||||
|
: `Change language, currently ${activeLocale.label}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
render={
|
||||||
|
<HeaderActionButton
|
||||||
|
aria-label={triggerLabel}
|
||||||
|
className="rounded-full"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<LocaleFlag country={activeLocale.country} className="size-6" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
className="w-44"
|
||||||
|
showArrow={{ cornerRadius: "lg", tailSize: "sm" }}
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={locale}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (isAppLocale(value)) {
|
||||||
|
setLocale(value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel>{menuLabel}</DropdownMenuLabel>
|
||||||
|
{appLocales.map((definition) => (
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
key={definition.locale}
|
||||||
|
className="py-2"
|
||||||
|
value={definition.locale}
|
||||||
|
>
|
||||||
|
<LocaleFlag country={definition.country} className="size-5" />
|
||||||
|
<span lang={definition.languageTag}>{definition.label}</span>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { LocaleDefinition } from "@workspace/i18n"
|
||||||
|
|
||||||
|
export type AppLocale = "en" | "zh-Hans"
|
||||||
|
export type AppLocaleCountry = "CN" | "US"
|
||||||
|
|
||||||
|
export interface AppLocaleDefinition extends LocaleDefinition {
|
||||||
|
country: AppLocaleCountry
|
||||||
|
locale: AppLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_APP_LOCALE: AppLocale = "zh-Hans"
|
||||||
|
|
||||||
|
export const appLocales = [
|
||||||
|
{
|
||||||
|
country: "US",
|
||||||
|
direction: "ltr",
|
||||||
|
languageTag: "en-US",
|
||||||
|
label: "English",
|
||||||
|
locale: "en",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
country: "CN",
|
||||||
|
direction: "ltr",
|
||||||
|
languageTag: "zh-CN",
|
||||||
|
label: "简体中文",
|
||||||
|
locale: "zh-Hans",
|
||||||
|
},
|
||||||
|
] as const satisfies readonly AppLocaleDefinition[]
|
||||||
|
|
||||||
|
export function isAppLocale(value: unknown): value is AppLocale {
|
||||||
|
return appLocales.some((definition) => definition.locale === value)
|
||||||
|
}
|
||||||
@@ -12,3 +12,843 @@ msgstr ""
|
|||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Plural-Forms: \n"
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:4
|
||||||
|
msgid "blocks.userMenu.accountPassword"
|
||||||
|
msgstr "Account password"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:4
|
||||||
|
msgid "blocks.appearance.active"
|
||||||
|
msgstr "Active"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:276
|
||||||
|
msgid "app.navigation.system-members-admins"
|
||||||
|
msgstr "Administrator accounts"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:148
|
||||||
|
msgid "app.navigation.after-sales-center"
|
||||||
|
msgstr "After-sales"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
|
||||||
|
msgid "blocks.appearance.color.amber"
|
||||||
|
msgstr "Amber"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:308
|
||||||
|
msgid "app.navigation.system-integration-api-keys"
|
||||||
|
msgstr "API keys"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:8
|
||||||
|
msgid "blocks.userMenu.appearance"
|
||||||
|
msgstr "Appearance"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:8
|
||||||
|
msgid "blocks.appearance.title"
|
||||||
|
msgstr "Appearance"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:92
|
||||||
|
msgid "app.navigation.content-articles"
|
||||||
|
msgstr "Articles"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:300
|
||||||
|
msgid "app.navigation.system-audit"
|
||||||
|
msgstr "Audit log"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:112
|
||||||
|
msgid "app.navigation.trade-orders-awaiting-payment"
|
||||||
|
msgstr "Awaiting payment"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:116
|
||||||
|
msgid "app.navigation.trade-orders-awaiting-shipment"
|
||||||
|
msgstr "Awaiting shipment"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
|
||||||
|
msgid "blocks.appearance.baseColor"
|
||||||
|
msgstr "Base color"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:220
|
||||||
|
msgid "app.navigation.loyalty-benefits"
|
||||||
|
msgstr "Benefits configuration"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:95
|
||||||
|
msgid "blocks.appearance.color.blue"
|
||||||
|
msgstr "Blue"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:48
|
||||||
|
msgid "app.navigation.catalog-product-brands"
|
||||||
|
msgstr "Brands"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:25
|
||||||
|
msgid "ui.breadcrumb.label"
|
||||||
|
msgstr "Breadcrumb"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:8
|
||||||
|
msgid "app.navigation.overview-dashboard"
|
||||||
|
msgstr "Business overview"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:128
|
||||||
|
msgid "app.navigation.trade-orders-canceled"
|
||||||
|
msgstr "Canceled orders"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:52
|
||||||
|
msgid "app.navigation.catalog-categories"
|
||||||
|
msgstr "Categories"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:56
|
||||||
|
msgid "app.navigation.catalog-category-tree"
|
||||||
|
msgstr "Category tree"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:244
|
||||||
|
msgid "app.navigation.report-channels"
|
||||||
|
msgstr "Channel analysis"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||||
|
msgid "blocks.chats.title"
|
||||||
|
msgstr "Chats"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:16
|
||||||
|
msgid "blocks.notifications.error.description"
|
||||||
|
msgstr "Check your network connection and try again."
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:29
|
||||||
|
msgid "ui.common.close"
|
||||||
|
msgstr "Close"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:33
|
||||||
|
msgid "ui.toast.close"
|
||||||
|
msgstr "Close notification"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:24
|
||||||
|
msgid "blocks.appearance.compact.title"
|
||||||
|
msgstr "Compact layout"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:124
|
||||||
|
msgid "app.navigation.trade-orders-completed"
|
||||||
|
msgstr "Completed orders"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:88
|
||||||
|
msgid "app.navigation.content-assets"
|
||||||
|
msgstr "Content"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:192
|
||||||
|
msgid "app.navigation.marketing-coupons"
|
||||||
|
msgstr "Coupons"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:100
|
||||||
|
msgid "app.navigation.content-pages"
|
||||||
|
msgstr "Custom pages"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:240
|
||||||
|
msgid "app.navigation.report-customers"
|
||||||
|
msgstr "Customer analysis"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:172
|
||||||
|
msgid "app.navigation.customer-profiles"
|
||||||
|
msgstr "Customer profiles"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:176
|
||||||
|
msgid "app.navigation.customer-segments"
|
||||||
|
msgstr "Customer segments"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:180
|
||||||
|
msgid "app.navigation.customer-tiers"
|
||||||
|
msgstr "Customer tiers"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:168
|
||||||
|
msgid "app.navigation.customer-center"
|
||||||
|
msgstr "Customers"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:164
|
||||||
|
msgid "app.navigation.customers-and-marketing"
|
||||||
|
msgstr "Customers and marketing"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:99
|
||||||
|
msgid "blocks.appearance.color.cyan"
|
||||||
|
msgstr "Cyan"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:28
|
||||||
|
msgid "blocks.appearance.mode.dark"
|
||||||
|
msgstr "Dark"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:36
|
||||||
|
msgid "blocks.appearance.theme.dark"
|
||||||
|
msgstr "Dark theme"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:224
|
||||||
|
msgid "app.navigation.data-and-finance"
|
||||||
|
msgstr "Data and finance"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:296
|
||||||
|
msgid "app.navigation.system-notification-logs"
|
||||||
|
msgstr "Delivery logs"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:136
|
||||||
|
msgid "app.navigation.fulfillment-deliveries"
|
||||||
|
msgstr "Delivery tasks"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:81
|
||||||
|
msgid "ui.sidebar.description"
|
||||||
|
msgstr "Displays the mobile sidebar."
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:103
|
||||||
|
msgid "blocks.appearance.color.emerald"
|
||||||
|
msgstr "Emerald"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:248
|
||||||
|
msgid "app.navigation.finance-center"
|
||||||
|
msgstr "Finance"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:196
|
||||||
|
msgid "app.navigation.marketing-flash-sales"
|
||||||
|
msgstr "Flash sales"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:107
|
||||||
|
msgid "blocks.appearance.color.fuchsia"
|
||||||
|
msgstr "Fuchsia"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:132
|
||||||
|
msgid "app.navigation.fulfillment-center"
|
||||||
|
msgstr "Fulfillment"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:200
|
||||||
|
msgid "app.navigation.marketing-gift-promotions"
|
||||||
|
msgstr "Gift promotions"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:53
|
||||||
|
msgid "ui.pagination.nextPage"
|
||||||
|
msgstr "Go to next page"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:69
|
||||||
|
msgid "ui.pagination.previousPage"
|
||||||
|
msgstr "Go to previous page"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:111
|
||||||
|
msgid "blocks.appearance.color.green"
|
||||||
|
msgstr "Green"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:120
|
||||||
|
msgid "app.navigation.trade-orders-shipped"
|
||||||
|
msgstr "In transit"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:72
|
||||||
|
msgid "app.navigation.inventory-inbound"
|
||||||
|
msgstr "Inbound orders"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:115
|
||||||
|
msgid "blocks.appearance.color.indigo"
|
||||||
|
msgstr "Indigo"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:304
|
||||||
|
msgid "app.navigation.system-integrations"
|
||||||
|
msgstr "Integrations"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:64
|
||||||
|
msgid "app.navigation.inventory-center"
|
||||||
|
msgstr "Inventory"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:84
|
||||||
|
msgid "app.navigation.inventory-alerts"
|
||||||
|
msgstr "Inventory alerts"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:68
|
||||||
|
msgid "app.navigation.inventory-overview"
|
||||||
|
msgstr "Inventory overview"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:260
|
||||||
|
msgid "app.navigation.finance-invoices"
|
||||||
|
msgstr "Invoices"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:24
|
||||||
|
msgid "blocks.notifications.justNow"
|
||||||
|
msgstr "just now"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:16
|
||||||
|
msgid "blocks.userMenu.keybindings"
|
||||||
|
msgstr "Keybindings"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:52
|
||||||
|
msgid "blocks.appearance.mode.light"
|
||||||
|
msgstr "Light"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:60
|
||||||
|
msgid "blocks.appearance.theme.light"
|
||||||
|
msgstr "Light theme"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:119
|
||||||
|
msgid "blocks.appearance.color.lime"
|
||||||
|
msgstr "Lime"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:37
|
||||||
|
msgid "ui.common.loading"
|
||||||
|
msgstr "Loading"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:28
|
||||||
|
msgid "blocks.notifications.loading"
|
||||||
|
msgstr "Loading notifications…"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:20
|
||||||
|
msgid "blocks.userMenu.logout"
|
||||||
|
msgstr "Log out"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:32
|
||||||
|
msgid "blocks.notifications.markAllAsRead"
|
||||||
|
msgstr "Mark all as read"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:188
|
||||||
|
msgid "app.navigation.marketing-campaigns"
|
||||||
|
msgstr "Marketing campaigns"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:123
|
||||||
|
msgid "blocks.appearance.color.mauve"
|
||||||
|
msgstr "Mauve"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:96
|
||||||
|
msgid "app.navigation.content-media"
|
||||||
|
msgstr "Media"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:208
|
||||||
|
msgid "app.navigation.loyalty-center"
|
||||||
|
msgstr "Membership benefits"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
|
||||||
|
msgid "blocks.appearance.color.mist"
|
||||||
|
msgstr "Mist"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:41
|
||||||
|
msgid "ui.common.more"
|
||||||
|
msgstr "More"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:45
|
||||||
|
msgid "ui.pagination.more"
|
||||||
|
msgstr "More pages"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:4
|
||||||
|
msgid "blocks.navigation.description"
|
||||||
|
msgstr "Navigate through the app."
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:12
|
||||||
|
msgid "blocks.navigation.title"
|
||||||
|
msgstr "Navigation"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:60
|
||||||
|
msgid "app.navigation.catalog-category-navigation"
|
||||||
|
msgstr "Navigation categories"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:131
|
||||||
|
msgid "blocks.appearance.color.neutral"
|
||||||
|
msgstr "Neutral"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:8
|
||||||
|
msgid "blocks.notifications.empty.description"
|
||||||
|
msgstr "New activity will appear here."
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:49
|
||||||
|
msgid "ui.pagination.next"
|
||||||
|
msgstr "Next"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:57
|
||||||
|
msgid "ui.carousel.nextSlide"
|
||||||
|
msgstr "Next slide"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
|
||||||
|
msgid "blocks.notifications.empty.title"
|
||||||
|
msgstr "No notifications"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:292
|
||||||
|
msgid "app.navigation.system-notification-templates"
|
||||||
|
msgstr "Notification templates"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:40
|
||||||
|
msgid "blocks.notifications.title"
|
||||||
|
msgstr "Notifications"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:288
|
||||||
|
msgid "app.navigation.system-notifications"
|
||||||
|
msgstr "Notifications"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:135
|
||||||
|
msgid "blocks.appearance.color.olive"
|
||||||
|
msgstr "Olive"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:12
|
||||||
|
msgid "blocks.breadcrumb.menuLabel"
|
||||||
|
msgstr "Open breadcrumb menu"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:8
|
||||||
|
msgid "blocks.navigation.open"
|
||||||
|
msgstr "Open navigation"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:12
|
||||||
|
msgid "blocks.appearance.command.description"
|
||||||
|
msgstr "Open theme and layout preferences"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:24
|
||||||
|
msgid "blocks.userMenu.open"
|
||||||
|
msgstr "Open user menu"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:139
|
||||||
|
msgid "blocks.appearance.color.orange"
|
||||||
|
msgstr "Orange"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:108
|
||||||
|
msgid "app.navigation.trade-orders"
|
||||||
|
msgstr "Orders"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:104
|
||||||
|
msgid "app.navigation.trade-and-fulfillment"
|
||||||
|
msgstr "Orders and fulfillment"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:272
|
||||||
|
msgid "app.navigation.system-members"
|
||||||
|
msgstr "Organization members"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:76
|
||||||
|
msgid "app.navigation.inventory-outbound"
|
||||||
|
msgstr "Outbound orders"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:4
|
||||||
|
msgid "app.navigation.overview"
|
||||||
|
msgstr "Overview"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:61
|
||||||
|
msgid "ui.pagination.label"
|
||||||
|
msgstr "Pagination"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:324
|
||||||
|
msgid "app.navigation.system-settings-payments"
|
||||||
|
msgstr "Payment channels"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:264
|
||||||
|
msgid "app.navigation.finance-payouts"
|
||||||
|
msgstr "Payout history"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:284
|
||||||
|
msgid "app.navigation.system-members-permissions"
|
||||||
|
msgstr "Permission policies"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:140
|
||||||
|
msgid "app.navigation.fulfillment-pickups"
|
||||||
|
msgstr "Pickup verification"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:143
|
||||||
|
msgid "blocks.appearance.color.pink"
|
||||||
|
msgstr "Pink"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:212
|
||||||
|
msgid "app.navigation.loyalty-points"
|
||||||
|
msgstr "Points rules"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:65
|
||||||
|
msgid "ui.pagination.previous"
|
||||||
|
msgstr "Previous"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:73
|
||||||
|
msgid "ui.carousel.previousSlide"
|
||||||
|
msgstr "Previous slide"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:68
|
||||||
|
msgid "blocks.appearance.primaryColor"
|
||||||
|
msgstr "Primary color"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:236
|
||||||
|
msgid "app.navigation.report-products"
|
||||||
|
msgstr "Product analysis"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:40
|
||||||
|
msgid "app.navigation.catalog-products-attributes"
|
||||||
|
msgstr "Product attributes"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:36
|
||||||
|
msgid "app.navigation.catalog-products-bundles"
|
||||||
|
msgstr "Product bundles"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:32
|
||||||
|
msgid "app.navigation.catalog-products-drafts"
|
||||||
|
msgstr "Product drafts"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:28
|
||||||
|
msgid "app.navigation.catalog-products-list"
|
||||||
|
msgstr "Product list"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:24
|
||||||
|
msgid "app.navigation.catalog-products"
|
||||||
|
msgstr "Products"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:20
|
||||||
|
msgid "app.navigation.catalog-and-inventory"
|
||||||
|
msgstr "Products and inventory"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:147
|
||||||
|
msgid "blocks.appearance.color.purple"
|
||||||
|
msgstr "Purple"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:12
|
||||||
|
msgid "app.navigation.overview-realtime"
|
||||||
|
msgstr "Real-time monitoring"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:256
|
||||||
|
msgid "app.navigation.finance-reconciliation"
|
||||||
|
msgstr "Reconciliation"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:332
|
||||||
|
msgid "app.navigation.system-recycle-bin"
|
||||||
|
msgstr "Recycle bin"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:151
|
||||||
|
msgid "blocks.appearance.color.red"
|
||||||
|
msgstr "Red"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:204
|
||||||
|
msgid "app.navigation.marketing-referrals"
|
||||||
|
msgstr "Referral rewards"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:32
|
||||||
|
msgid "blocks.refresh.label"
|
||||||
|
msgstr "Refresh current page data"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:28
|
||||||
|
msgid "blocks.refresh.description"
|
||||||
|
msgstr "Refresh the current page data"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:156
|
||||||
|
msgid "app.navigation.after-sales-refunds"
|
||||||
|
msgstr "Refund review"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:228
|
||||||
|
msgid "app.navigation.report-center"
|
||||||
|
msgstr "Reports"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:152
|
||||||
|
msgid "app.navigation.after-sales-returns"
|
||||||
|
msgstr "Return requests"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:184
|
||||||
|
msgid "app.navigation.customer-risk-list"
|
||||||
|
msgstr "Risk list"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:280
|
||||||
|
msgid "app.navigation.system-members-roles"
|
||||||
|
msgstr "Roles"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:155
|
||||||
|
msgid "blocks.appearance.color.rose"
|
||||||
|
msgstr "Rose"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:232
|
||||||
|
msgid "app.navigation.report-sales"
|
||||||
|
msgstr "Sales analysis"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:252
|
||||||
|
msgid "app.navigation.finance-settlements"
|
||||||
|
msgstr "Settlement statements"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:144
|
||||||
|
msgid "app.navigation.fulfillment-freight-rules"
|
||||||
|
msgstr "Shipping rules"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:328
|
||||||
|
msgid "app.navigation.system-settings-shipping"
|
||||||
|
msgstr "Shipping settings"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:77
|
||||||
|
msgid "ui.sidebar.title"
|
||||||
|
msgstr "Sidebar"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:159
|
||||||
|
msgid "blocks.appearance.color.sky"
|
||||||
|
msgstr "Sky"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:44
|
||||||
|
msgid "app.navigation.catalog-product-specifications"
|
||||||
|
msgstr "Specification templates"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:80
|
||||||
|
msgid "app.navigation.inventory-transfers"
|
||||||
|
msgstr "Stock transfers"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:163
|
||||||
|
msgid "blocks.appearance.color.stone"
|
||||||
|
msgstr "Stone"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:320
|
||||||
|
msgid "app.navigation.system-settings-store"
|
||||||
|
msgstr "Store information"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:216
|
||||||
|
msgid "app.navigation.loyalty-stored-value"
|
||||||
|
msgstr "Stored-value accounts"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:84
|
||||||
|
msgid "blocks.appearance.toggleTheme.description"
|
||||||
|
msgstr "Switch between light and dark themes"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:44
|
||||||
|
msgid "blocks.appearance.mode.system"
|
||||||
|
msgstr "System"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:268
|
||||||
|
msgid "app.navigation.system-management"
|
||||||
|
msgstr "System management"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:316
|
||||||
|
msgid "app.navigation.system-settings"
|
||||||
|
msgstr "System settings"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:16
|
||||||
|
msgid "app.navigation.overview-tasks"
|
||||||
|
msgstr "Tasks"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:167
|
||||||
|
msgid "blocks.appearance.color.taupe"
|
||||||
|
msgstr "Taupe"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:171
|
||||||
|
msgid "blocks.appearance.color.teal"
|
||||||
|
msgstr "Teal"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:48
|
||||||
|
msgid "blocks.appearance.mode.system.description"
|
||||||
|
msgstr "The interface theme will follow the system appearance"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:32
|
||||||
|
msgid "blocks.appearance.mode.dark.description"
|
||||||
|
msgstr "The interface will always use the dark theme"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:56
|
||||||
|
msgid "blocks.appearance.mode.light.description"
|
||||||
|
msgstr "The interface will always use the light theme"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:72
|
||||||
|
msgid "blocks.appearance.description"
|
||||||
|
msgstr "Theme and layout preferences"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:76
|
||||||
|
msgid "blocks.appearance.mode.title"
|
||||||
|
msgstr "Theme mode"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:40
|
||||||
|
msgid "blocks.appearance.theme.dark.description"
|
||||||
|
msgstr "This theme is used when the system is in dark mode"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:64
|
||||||
|
msgid "blocks.appearance.theme.light.description"
|
||||||
|
msgstr "This theme is used when the system is in light mode"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:85
|
||||||
|
msgid "ui.sidebar.toggle"
|
||||||
|
msgstr "Toggle sidebar"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:80
|
||||||
|
msgid "blocks.appearance.toggleTheme"
|
||||||
|
msgstr "Toggle theme"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:160
|
||||||
|
msgid "app.navigation.after-sales-disputes"
|
||||||
|
msgstr "Transaction disputes"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
|
||||||
|
msgid "blocks.notifications.retry"
|
||||||
|
msgstr "Try again"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:20
|
||||||
|
msgid "blocks.notifications.error.title"
|
||||||
|
msgstr "Unable to load notifications"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:20
|
||||||
|
msgid "blocks.appearance.compact.description"
|
||||||
|
msgstr "Use a compact width for the main page content"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:36
|
||||||
|
msgid "blocks.userMenu.title"
|
||||||
|
msgstr "User menu"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:44
|
||||||
|
msgid "blocks.notifications.viewAll"
|
||||||
|
msgstr "View all"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:4
|
||||||
|
msgid "blocks.notifications.description"
|
||||||
|
msgstr "View and manage notifications"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:175
|
||||||
|
msgid "blocks.appearance.color.violet"
|
||||||
|
msgstr "Violet"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:312
|
||||||
|
msgid "app.navigation.system-integration-webhooks"
|
||||||
|
msgstr "Webhook configuration"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
|
||||||
|
msgid "blocks.appearance.color.yellow"
|
||||||
|
msgstr "Yellow"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:183
|
||||||
|
msgid "blocks.appearance.color.zinc"
|
||||||
|
msgstr "Zinc"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,3 +12,843 @@ msgstr ""
|
|||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Plural-Forms: \n"
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:4
|
||||||
|
msgid "blocks.userMenu.accountPassword"
|
||||||
|
msgstr "账号密码"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:4
|
||||||
|
msgid "blocks.appearance.active"
|
||||||
|
msgstr "使用中"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:276
|
||||||
|
msgid "app.navigation.system-members-admins"
|
||||||
|
msgstr "管理员账号"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:148
|
||||||
|
msgid "app.navigation.after-sales-center"
|
||||||
|
msgstr "售后中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:91
|
||||||
|
msgid "blocks.appearance.color.amber"
|
||||||
|
msgstr "琥珀色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:308
|
||||||
|
msgid "app.navigation.system-integration-api-keys"
|
||||||
|
msgstr "API 密钥"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:8
|
||||||
|
msgid "blocks.userMenu.appearance"
|
||||||
|
msgstr "界面外观"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:8
|
||||||
|
msgid "blocks.appearance.title"
|
||||||
|
msgstr "界面外观"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:92
|
||||||
|
msgid "app.navigation.content-articles"
|
||||||
|
msgstr "内容文章"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:300
|
||||||
|
msgid "app.navigation.system-audit"
|
||||||
|
msgstr "操作审计"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:112
|
||||||
|
msgid "app.navigation.trade-orders-awaiting-payment"
|
||||||
|
msgstr "待付款订单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:116
|
||||||
|
msgid "app.navigation.trade-orders-awaiting-shipment"
|
||||||
|
msgstr "待发货订单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:16
|
||||||
|
msgid "blocks.appearance.baseColor"
|
||||||
|
msgstr "基础颜色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:220
|
||||||
|
msgid "app.navigation.loyalty-benefits"
|
||||||
|
msgstr "权益配置"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:95
|
||||||
|
msgid "blocks.appearance.color.blue"
|
||||||
|
msgstr "蓝色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:48
|
||||||
|
msgid "app.navigation.catalog-product-brands"
|
||||||
|
msgstr "品牌资料"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:25
|
||||||
|
msgid "ui.breadcrumb.label"
|
||||||
|
msgstr "面包屑导航"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:8
|
||||||
|
msgid "app.navigation.overview-dashboard"
|
||||||
|
msgstr "经营总览"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:128
|
||||||
|
msgid "app.navigation.trade-orders-canceled"
|
||||||
|
msgstr "已取消订单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:52
|
||||||
|
msgid "app.navigation.catalog-categories"
|
||||||
|
msgstr "分类目录"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:56
|
||||||
|
msgid "app.navigation.catalog-category-tree"
|
||||||
|
msgstr "分类树"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:244
|
||||||
|
msgid "app.navigation.report-channels"
|
||||||
|
msgstr "渠道分析"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/chats/messages.ts:4
|
||||||
|
msgid "blocks.chats.title"
|
||||||
|
msgstr "聊天"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:16
|
||||||
|
msgid "blocks.notifications.error.description"
|
||||||
|
msgstr "请检查网络连接后重试。"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:29
|
||||||
|
msgid "ui.common.close"
|
||||||
|
msgstr "关闭"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:33
|
||||||
|
msgid "ui.toast.close"
|
||||||
|
msgstr "关闭通知"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:24
|
||||||
|
msgid "blocks.appearance.compact.title"
|
||||||
|
msgstr "紧凑布局"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:124
|
||||||
|
msgid "app.navigation.trade-orders-completed"
|
||||||
|
msgstr "已完成订单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:88
|
||||||
|
msgid "app.navigation.content-assets"
|
||||||
|
msgstr "内容素材"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:192
|
||||||
|
msgid "app.navigation.marketing-coupons"
|
||||||
|
msgstr "优惠券管理"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:100
|
||||||
|
msgid "app.navigation.content-pages"
|
||||||
|
msgstr "自定义页面"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:240
|
||||||
|
msgid "app.navigation.report-customers"
|
||||||
|
msgstr "客群分析"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:172
|
||||||
|
msgid "app.navigation.customer-profiles"
|
||||||
|
msgstr "客户档案"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:176
|
||||||
|
msgid "app.navigation.customer-segments"
|
||||||
|
msgstr "客户分群"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:180
|
||||||
|
msgid "app.navigation.customer-tiers"
|
||||||
|
msgstr "客户等级"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:168
|
||||||
|
msgid "app.navigation.customer-center"
|
||||||
|
msgstr "客户中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:164
|
||||||
|
msgid "app.navigation.customers-and-marketing"
|
||||||
|
msgstr "客户与营销"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:99
|
||||||
|
msgid "blocks.appearance.color.cyan"
|
||||||
|
msgstr "青色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:28
|
||||||
|
msgid "blocks.appearance.mode.dark"
|
||||||
|
msgstr "深色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:36
|
||||||
|
msgid "blocks.appearance.theme.dark"
|
||||||
|
msgstr "深色主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:224
|
||||||
|
msgid "app.navigation.data-and-finance"
|
||||||
|
msgstr "数据与财务"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:296
|
||||||
|
msgid "app.navigation.system-notification-logs"
|
||||||
|
msgstr "发送记录"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:136
|
||||||
|
msgid "app.navigation.fulfillment-deliveries"
|
||||||
|
msgstr "配送任务"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:81
|
||||||
|
msgid "ui.sidebar.description"
|
||||||
|
msgstr "显示移动端侧栏。"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:103
|
||||||
|
msgid "blocks.appearance.color.emerald"
|
||||||
|
msgstr "翠绿色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:248
|
||||||
|
msgid "app.navigation.finance-center"
|
||||||
|
msgstr "财务中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:196
|
||||||
|
msgid "app.navigation.marketing-flash-sales"
|
||||||
|
msgstr "限时折扣"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:107
|
||||||
|
msgid "blocks.appearance.color.fuchsia"
|
||||||
|
msgstr "紫红色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:132
|
||||||
|
msgid "app.navigation.fulfillment-center"
|
||||||
|
msgstr "配送履约"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:200
|
||||||
|
msgid "app.navigation.marketing-gift-promotions"
|
||||||
|
msgstr "满赠活动"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:53
|
||||||
|
msgid "ui.pagination.nextPage"
|
||||||
|
msgstr "前往下一页"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:69
|
||||||
|
msgid "ui.pagination.previousPage"
|
||||||
|
msgstr "前往上一页"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:111
|
||||||
|
msgid "blocks.appearance.color.green"
|
||||||
|
msgstr "绿色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:120
|
||||||
|
msgid "app.navigation.trade-orders-shipped"
|
||||||
|
msgstr "配送中订单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:72
|
||||||
|
msgid "app.navigation.inventory-inbound"
|
||||||
|
msgstr "入库单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:115
|
||||||
|
msgid "blocks.appearance.color.indigo"
|
||||||
|
msgstr "靛蓝色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:304
|
||||||
|
msgid "app.navigation.system-integrations"
|
||||||
|
msgstr "开放集成"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:64
|
||||||
|
msgid "app.navigation.inventory-center"
|
||||||
|
msgstr "库存中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:84
|
||||||
|
msgid "app.navigation.inventory-alerts"
|
||||||
|
msgstr "库存预警"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:68
|
||||||
|
msgid "app.navigation.inventory-overview"
|
||||||
|
msgstr "库存总览"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:260
|
||||||
|
msgid "app.navigation.finance-invoices"
|
||||||
|
msgstr "发票管理"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:24
|
||||||
|
msgid "blocks.notifications.justNow"
|
||||||
|
msgstr "刚刚"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:16
|
||||||
|
msgid "blocks.userMenu.keybindings"
|
||||||
|
msgstr "按键映射"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:52
|
||||||
|
msgid "blocks.appearance.mode.light"
|
||||||
|
msgstr "浅色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:60
|
||||||
|
msgid "blocks.appearance.theme.light"
|
||||||
|
msgstr "浅色主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:119
|
||||||
|
msgid "blocks.appearance.color.lime"
|
||||||
|
msgstr "青柠色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:37
|
||||||
|
msgid "ui.common.loading"
|
||||||
|
msgstr "正在加载"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:28
|
||||||
|
msgid "blocks.notifications.loading"
|
||||||
|
msgstr "正在加载通知…"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:20
|
||||||
|
msgid "blocks.userMenu.logout"
|
||||||
|
msgstr "退出登录"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:32
|
||||||
|
msgid "blocks.notifications.markAllAsRead"
|
||||||
|
msgstr "全部标记为已读"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:188
|
||||||
|
msgid "app.navigation.marketing-campaigns"
|
||||||
|
msgstr "营销活动"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:123
|
||||||
|
msgid "blocks.appearance.color.mauve"
|
||||||
|
msgstr "紫灰"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:96
|
||||||
|
msgid "app.navigation.content-media"
|
||||||
|
msgstr "媒体资源"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:208
|
||||||
|
msgid "app.navigation.loyalty-center"
|
||||||
|
msgstr "会员权益"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:127
|
||||||
|
msgid "blocks.appearance.color.mist"
|
||||||
|
msgstr "雾灰"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:41
|
||||||
|
msgid "ui.common.more"
|
||||||
|
msgstr "更多"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:45
|
||||||
|
msgid "ui.pagination.more"
|
||||||
|
msgstr "更多页面"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:4
|
||||||
|
msgid "blocks.navigation.description"
|
||||||
|
msgstr "浏览应用中的页面。"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:12
|
||||||
|
msgid "blocks.navigation.title"
|
||||||
|
msgstr "导航"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:60
|
||||||
|
msgid "app.navigation.catalog-category-navigation"
|
||||||
|
msgstr "导航分类"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:131
|
||||||
|
msgid "blocks.appearance.color.neutral"
|
||||||
|
msgstr "中性灰"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:8
|
||||||
|
msgid "blocks.notifications.empty.description"
|
||||||
|
msgstr "新的动态会显示在这里。"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:49
|
||||||
|
msgid "ui.pagination.next"
|
||||||
|
msgstr "下一页"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:57
|
||||||
|
msgid "ui.carousel.nextSlide"
|
||||||
|
msgstr "下一张"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:12
|
||||||
|
msgid "blocks.notifications.empty.title"
|
||||||
|
msgstr "暂无通知"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:292
|
||||||
|
msgid "app.navigation.system-notification-templates"
|
||||||
|
msgstr "通知模板"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:40
|
||||||
|
msgid "blocks.notifications.title"
|
||||||
|
msgstr "通知"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:288
|
||||||
|
msgid "app.navigation.system-notifications"
|
||||||
|
msgstr "消息中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:135
|
||||||
|
msgid "blocks.appearance.color.olive"
|
||||||
|
msgstr "橄榄灰"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:12
|
||||||
|
msgid "blocks.breadcrumb.menuLabel"
|
||||||
|
msgstr "打开面包屑菜单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/navigation/messages.ts:8
|
||||||
|
msgid "blocks.navigation.open"
|
||||||
|
msgstr "打开导航"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:12
|
||||||
|
msgid "blocks.appearance.command.description"
|
||||||
|
msgstr "打开主题与布局偏好"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:24
|
||||||
|
msgid "blocks.userMenu.open"
|
||||||
|
msgstr "打开用户菜单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:139
|
||||||
|
msgid "blocks.appearance.color.orange"
|
||||||
|
msgstr "橙色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:108
|
||||||
|
msgid "app.navigation.trade-orders"
|
||||||
|
msgstr "订单中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:104
|
||||||
|
msgid "app.navigation.trade-and-fulfillment"
|
||||||
|
msgstr "交易与履约"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:272
|
||||||
|
msgid "app.navigation.system-members"
|
||||||
|
msgstr "组织成员"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:76
|
||||||
|
msgid "app.navigation.inventory-outbound"
|
||||||
|
msgstr "出库单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:4
|
||||||
|
msgid "app.navigation.overview"
|
||||||
|
msgstr "概览"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:61
|
||||||
|
msgid "ui.pagination.label"
|
||||||
|
msgstr "分页导航"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:324
|
||||||
|
msgid "app.navigation.system-settings-payments"
|
||||||
|
msgstr "支付渠道"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:264
|
||||||
|
msgid "app.navigation.finance-payouts"
|
||||||
|
msgstr "打款记录"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:284
|
||||||
|
msgid "app.navigation.system-members-permissions"
|
||||||
|
msgstr "权限策略"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:140
|
||||||
|
msgid "app.navigation.fulfillment-pickups"
|
||||||
|
msgstr "自提核销"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:143
|
||||||
|
msgid "blocks.appearance.color.pink"
|
||||||
|
msgstr "粉色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:212
|
||||||
|
msgid "app.navigation.loyalty-points"
|
||||||
|
msgstr "积分规则"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:65
|
||||||
|
msgid "ui.pagination.previous"
|
||||||
|
msgstr "上一页"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:73
|
||||||
|
msgid "ui.carousel.previousSlide"
|
||||||
|
msgstr "上一张"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:68
|
||||||
|
msgid "blocks.appearance.primaryColor"
|
||||||
|
msgstr "主要颜色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:236
|
||||||
|
msgid "app.navigation.report-products"
|
||||||
|
msgstr "商品分析"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:40
|
||||||
|
msgid "app.navigation.catalog-products-attributes"
|
||||||
|
msgstr "商品属性"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:36
|
||||||
|
msgid "app.navigation.catalog-products-bundles"
|
||||||
|
msgstr "组合商品"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:32
|
||||||
|
msgid "app.navigation.catalog-products-drafts"
|
||||||
|
msgstr "商品草稿"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:28
|
||||||
|
msgid "app.navigation.catalog-products-list"
|
||||||
|
msgstr "商品列表"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:24
|
||||||
|
msgid "app.navigation.catalog-products"
|
||||||
|
msgstr "商品中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:20
|
||||||
|
msgid "app.navigation.catalog-and-inventory"
|
||||||
|
msgstr "商品与库存"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:147
|
||||||
|
msgid "blocks.appearance.color.purple"
|
||||||
|
msgstr "紫色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:12
|
||||||
|
msgid "app.navigation.overview-realtime"
|
||||||
|
msgstr "实时监控"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:256
|
||||||
|
msgid "app.navigation.finance-reconciliation"
|
||||||
|
msgstr "对账管理"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:332
|
||||||
|
msgid "app.navigation.system-recycle-bin"
|
||||||
|
msgstr "回收站"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:151
|
||||||
|
msgid "blocks.appearance.color.red"
|
||||||
|
msgstr "红色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:204
|
||||||
|
msgid "app.navigation.marketing-referrals"
|
||||||
|
msgstr "邀请有礼"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:32
|
||||||
|
msgid "blocks.refresh.label"
|
||||||
|
msgstr "刷新当前页面数据"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:28
|
||||||
|
msgid "blocks.refresh.description"
|
||||||
|
msgstr "刷新当前页面的数据"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:156
|
||||||
|
msgid "app.navigation.after-sales-refunds"
|
||||||
|
msgstr "退款审核"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:228
|
||||||
|
msgid "app.navigation.report-center"
|
||||||
|
msgstr "数据报表"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:152
|
||||||
|
msgid "app.navigation.after-sales-returns"
|
||||||
|
msgstr "退货申请"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:184
|
||||||
|
msgid "app.navigation.customer-risk-list"
|
||||||
|
msgstr "风险名单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:280
|
||||||
|
msgid "app.navigation.system-members-roles"
|
||||||
|
msgstr "角色管理"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:155
|
||||||
|
msgid "blocks.appearance.color.rose"
|
||||||
|
msgstr "玫红色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:232
|
||||||
|
msgid "app.navigation.report-sales"
|
||||||
|
msgstr "销售分析"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:252
|
||||||
|
msgid "app.navigation.finance-settlements"
|
||||||
|
msgstr "结算账单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:144
|
||||||
|
msgid "app.navigation.fulfillment-freight-rules"
|
||||||
|
msgstr "运费规则"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:328
|
||||||
|
msgid "app.navigation.system-settings-shipping"
|
||||||
|
msgstr "配送设置"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:77
|
||||||
|
msgid "ui.sidebar.title"
|
||||||
|
msgstr "侧栏"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:159
|
||||||
|
msgid "blocks.appearance.color.sky"
|
||||||
|
msgstr "天蓝色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:44
|
||||||
|
msgid "app.navigation.catalog-product-specifications"
|
||||||
|
msgstr "规格模板"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:80
|
||||||
|
msgid "app.navigation.inventory-transfers"
|
||||||
|
msgstr "调拨单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:163
|
||||||
|
msgid "blocks.appearance.color.stone"
|
||||||
|
msgstr "石灰"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:320
|
||||||
|
msgid "app.navigation.system-settings-store"
|
||||||
|
msgstr "门店资料"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:216
|
||||||
|
msgid "app.navigation.loyalty-stored-value"
|
||||||
|
msgstr "储值账户"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:84
|
||||||
|
msgid "blocks.appearance.toggleTheme.description"
|
||||||
|
msgstr "在浅色与深色主题之间切换"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:44
|
||||||
|
msgid "blocks.appearance.mode.system"
|
||||||
|
msgstr "系统"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:268
|
||||||
|
msgid "app.navigation.system-management"
|
||||||
|
msgstr "系统管理"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:316
|
||||||
|
msgid "app.navigation.system-settings"
|
||||||
|
msgstr "系统配置"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:16
|
||||||
|
msgid "app.navigation.overview-tasks"
|
||||||
|
msgstr "待办中心"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:167
|
||||||
|
msgid "blocks.appearance.color.taupe"
|
||||||
|
msgstr "灰褐"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:171
|
||||||
|
msgid "blocks.appearance.color.teal"
|
||||||
|
msgstr "蓝绿色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:48
|
||||||
|
msgid "blocks.appearance.mode.system.description"
|
||||||
|
msgstr "界面主题将跟随系统外观设置"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:32
|
||||||
|
msgid "blocks.appearance.mode.dark.description"
|
||||||
|
msgstr "界面将始终使用深色主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:56
|
||||||
|
msgid "blocks.appearance.mode.light.description"
|
||||||
|
msgstr "界面将始终使用浅色主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:72
|
||||||
|
msgid "blocks.appearance.description"
|
||||||
|
msgstr "主题与布局偏好"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:76
|
||||||
|
msgid "blocks.appearance.mode.title"
|
||||||
|
msgstr "主题模式"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:40
|
||||||
|
msgid "blocks.appearance.theme.dark.description"
|
||||||
|
msgstr "当系统设置为深色模式时,将使用此主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:64
|
||||||
|
msgid "blocks.appearance.theme.light.description"
|
||||||
|
msgstr "当系统设置为浅色模式时,将使用此主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/ui/src/components/i18n.tsx:85
|
||||||
|
msgid "ui.sidebar.toggle"
|
||||||
|
msgstr "切换侧栏"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:80
|
||||||
|
msgid "blocks.appearance.toggleTheme"
|
||||||
|
msgstr "切换主题"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:160
|
||||||
|
msgid "app.navigation.after-sales-disputes"
|
||||||
|
msgstr "交易纠纷"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:36
|
||||||
|
msgid "blocks.notifications.retry"
|
||||||
|
msgstr "重新加载"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:20
|
||||||
|
msgid "blocks.notifications.error.title"
|
||||||
|
msgstr "无法加载通知"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:20
|
||||||
|
msgid "blocks.appearance.compact.description"
|
||||||
|
msgstr "启用后,页面主内容将使用紧凑宽度"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/layout/messages.ts:36
|
||||||
|
msgid "blocks.userMenu.title"
|
||||||
|
msgstr "用户菜单"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:44
|
||||||
|
msgid "blocks.notifications.viewAll"
|
||||||
|
msgstr "查看全部"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/notifications/messages.ts:4
|
||||||
|
msgid "blocks.notifications.description"
|
||||||
|
msgstr "查看并处理通知"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:175
|
||||||
|
msgid "blocks.appearance.color.violet"
|
||||||
|
msgstr "紫罗兰色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: src/data/navigation-messages.ts:312
|
||||||
|
msgid "app.navigation.system-integration-webhooks"
|
||||||
|
msgstr "Webhook 配置"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:179
|
||||||
|
msgid "blocks.appearance.color.yellow"
|
||||||
|
msgstr "黄色"
|
||||||
|
|
||||||
|
#. js-lingui-explicit-id
|
||||||
|
#: ../../packages/blocks/src/blocks/appearance/messages.ts:183
|
||||||
|
msgid "blocks.appearance.color.zinc"
|
||||||
|
msgstr "锌灰"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,10 +24,19 @@ import { HotkeysProvider } from "@tanstack/react-hotkeys"
|
|||||||
import { TooltipProvider } from "@workspace/ui/components/tooltip"
|
import { TooltipProvider } from "@workspace/ui/components/tooltip"
|
||||||
import { QueryClientProvider } from "@tanstack/react-query"
|
import { QueryClientProvider } from "@tanstack/react-query"
|
||||||
import { AppI18nProvider } from "@/i18n/app-i18n-provider"
|
import { AppI18nProvider } from "@/i18n/app-i18n-provider"
|
||||||
|
import { getAppLocale } from "@/server/app-locale.functions"
|
||||||
|
import type { AppLocale } from "@/i18n/app-locales"
|
||||||
import { queryClient } from "@/lib/query-client"
|
import { queryClient } from "@/lib/query-client"
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
loader: () => getUiState(),
|
loader: async () => {
|
||||||
|
const [initialState, initialLocale] = await Promise.all([
|
||||||
|
getUiState(),
|
||||||
|
getAppLocale(),
|
||||||
|
])
|
||||||
|
|
||||||
|
return { initialLocale, initialState }
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ charSet: "utf-8" },
|
{ charSet: "utf-8" },
|
||||||
@@ -48,11 +57,11 @@ export const Route = createRootRoute({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
const initialState = Route.useLoaderData()
|
const { initialLocale, initialState } = Route.useLoaderData()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RootDocument initialState={initialState}>
|
<RootDocument initialLocale={initialLocale} initialState={initialState}>
|
||||||
<AppI18nProvider>
|
<AppI18nProvider locale={initialLocale}>
|
||||||
<BreakpointReactor />
|
<BreakpointReactor />
|
||||||
<UiStateProvider
|
<UiStateProvider
|
||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
@@ -94,9 +103,11 @@ function getUiInitializationScript(state: UiState) {
|
|||||||
|
|
||||||
function RootDocument({
|
function RootDocument({
|
||||||
children,
|
children,
|
||||||
|
initialLocale,
|
||||||
initialState,
|
initialState,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
initialLocale: AppLocale
|
||||||
initialState: UiState
|
initialState: UiState
|
||||||
}>) {
|
}>) {
|
||||||
const themeMode = initialState["theme-mode"]
|
const themeMode = initialState["theme-mode"]
|
||||||
@@ -110,7 +121,7 @@ function RootDocument({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="zh-CN"
|
lang={initialLocale === "zh-Hans" ? "zh-CN" : "en-US"}
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className={className || undefined}
|
className={className || undefined}
|
||||||
style={themeMode === "system" ? undefined : { colorScheme: themeMode }}
|
style={themeMode === "system" ? undefined : { colorScheme: themeMode }}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
fetchMockNotifications,
|
fetchMockNotifications,
|
||||||
notificationsQueryKey,
|
notificationsQueryKey,
|
||||||
} from "@/data/notifications"
|
} from "@/data/notifications"
|
||||||
|
import { AppLanguageSwitcher } from "@/i18n/app-language-switcher"
|
||||||
|
|
||||||
export const Route = createFileRoute("/_app")({
|
export const Route = createFileRoute("/_app")({
|
||||||
component: ApplicationLayoutRoute,
|
component: ApplicationLayoutRoute,
|
||||||
@@ -17,6 +18,7 @@ function ApplicationLayoutRoute() {
|
|||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
chatThreads={chatThreads}
|
chatThreads={chatThreads}
|
||||||
|
headerActions={<AppLanguageSwitcher />}
|
||||||
navigationGroups={primaryNavigationGroups}
|
navigationGroups={primaryNavigationGroups}
|
||||||
notifications={{
|
notifications={{
|
||||||
queryFn: fetchMockNotifications,
|
queryFn: fetchMockNotifications,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createServerFn } from "@tanstack/react-start"
|
||||||
|
|
||||||
|
import { isAppLocale } from "@/i18n/app-locales"
|
||||||
|
import { readAppLocale, writeAppLocale } from "./app-locale.server"
|
||||||
|
|
||||||
|
export const getAppLocale = createServerFn({ method: "GET" }).handler(() => {
|
||||||
|
return readAppLocale()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setAppLocale = createServerFn({ method: "POST" })
|
||||||
|
.validator((value: unknown) => {
|
||||||
|
if (!isAppLocale(value)) {
|
||||||
|
throw new Error("Invalid app locale")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
.handler(({ data }) => {
|
||||||
|
writeAppLocale(data)
|
||||||
|
})
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import "@tanstack/react-start/server-only"
|
||||||
|
import { getCookie, setCookie } from "@tanstack/react-start/server"
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_APP_LOCALE,
|
||||||
|
isAppLocale,
|
||||||
|
type AppLocale,
|
||||||
|
} from "@/i18n/app-locales"
|
||||||
|
|
||||||
|
const APP_LOCALE_COOKIE = "ui-locale"
|
||||||
|
const COOKIE_MAX_AGE = 60 * 60 * 24 * 365
|
||||||
|
|
||||||
|
export function readAppLocale(): AppLocale {
|
||||||
|
const locale = getCookie(APP_LOCALE_COOKIE)
|
||||||
|
|
||||||
|
return isAppLocale(locale) ? locale : DEFAULT_APP_LOCALE
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeAppLocale(locale: AppLocale) {
|
||||||
|
setCookie(APP_LOCALE_COOKIE, locale, {
|
||||||
|
maxAge: COOKIE_MAX_AGE,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "lax",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"resolveJsonModule": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
@@ -21,5 +20,5 @@
|
|||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts", "lingui.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import react from "@vitejs/plugin-react"
|
||||||
|
import { defineConfig } from "vitest/config"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -30,8 +30,10 @@
|
|||||||
"@workspace/blocks": "workspace:*",
|
"@workspace/blocks": "workspace:*",
|
||||||
"@workspace/i18n": "workspace:*",
|
"@workspace/i18n": "workspace:*",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
|
"country-flag-icons": "^1.6.20",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
|
"react-day-picker": "^10.0.1",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
},
|
},
|
||||||
@@ -42,6 +44,7 @@
|
|||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@vitejs/plugin-react": "^6",
|
"@vitejs/plugin-react": "^6",
|
||||||
|
"jsdom": "^30.0.1",
|
||||||
"nitro": "^3.0.260610-beta",
|
"nitro": "^3.0.260610-beta",
|
||||||
"typescript": "~6",
|
"typescript": "~6",
|
||||||
"vite": "^8",
|
"vite": "^8",
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
"@tanstack/react-hotkeys": "^0.10.0",
|
"@tanstack/react-hotkeys": "^0.10.0",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
"@tanstack/react-router": "^1.170.18",
|
"@tanstack/react-router": "^1.170.18",
|
||||||
|
"@workspace/i18n": "workspace:*",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
@@ -86,7 +90,9 @@
|
|||||||
"@lingui/core": "^6",
|
"@lingui/core": "^6",
|
||||||
"@lingui/react": "^6",
|
"@lingui/react": "^6",
|
||||||
"@vitejs/plugin-react": "^6",
|
"@vitejs/plugin-react": "^6",
|
||||||
|
"ansi-to-react": "^6.2.6",
|
||||||
"cac": "^7.0.0",
|
"cac": "^7.0.0",
|
||||||
|
"lucide-react": "^1.27.0",
|
||||||
"open": "^11.0.0",
|
"open": "^11.0.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
@@ -687,12 +693,16 @@
|
|||||||
|
|
||||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
|
|
||||||
|
"anser": ["anser@2.3.5", "", {}, "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ=="],
|
||||||
|
|
||||||
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||||
|
|
||||||
|
"ansi-to-react": ["ansi-to-react@6.2.6", "", { "dependencies": { "anser": "^2.3.2", "escape-carriage": "^1.3.1", "linkify-it": "^3.0.3" }, "peerDependencies": { "react": "^16.3.2 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.3.2 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Eqi0iaMK5OZ3jsVFxWvU2B74UZBnGuHlkflKMX6wTOeH+luy9KE2O0gUkc2PxhIP1R4IO0xohv62UMFInQOSeg=="],
|
||||||
|
|
||||||
"ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="],
|
"ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="],
|
||||||
|
|
||||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
@@ -795,6 +805,8 @@
|
|||||||
|
|
||||||
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
||||||
|
|
||||||
|
"country-flag-icons": ["country-flag-icons@1.6.20", "", {}, "sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw=="],
|
||||||
|
|
||||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||||
|
|
||||||
"crossws": ["crossws@0.4.10", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA=="],
|
"crossws": ["crossws@0.4.10", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA=="],
|
||||||
@@ -913,6 +925,8 @@
|
|||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"escape-carriage": ["escape-carriage@1.3.1", "", {}, "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw=="],
|
||||||
|
|
||||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||||
|
|
||||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||||
@@ -1127,6 +1141,8 @@
|
|||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
|
"linkify-it": ["linkify-it@3.0.3", "", { "dependencies": { "uc.micro": "^1.0.1" } }, "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ=="],
|
||||||
|
|
||||||
"local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="],
|
"local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
"locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||||
@@ -1453,6 +1469,8 @@
|
|||||||
|
|
||||||
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
|
"uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="],
|
||||||
|
|
||||||
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
|
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
|
||||||
|
|
||||||
"undici": ["undici@8.9.0", "", {}, "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA=="],
|
"undici": ["undici@8.9.0", "", {}, "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA=="],
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@tanstack/react-hotkeys": "^0.10.0",
|
"@tanstack/react-hotkeys": "^0.10.0",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
"@tanstack/react-router": "^1.170.18",
|
"@tanstack/react-router": "^1.170.18",
|
||||||
|
"@workspace/i18n": "workspace:*",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
"lucide-react": "^1.27.0",
|
"lucide-react": "^1.27.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
@@ -33,12 +34,22 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
"./globals.css": "./src/styles/globals.css",
|
"./globals.css": "./src/styles/globals.css",
|
||||||
"./appearance": "./src/blocks/appearance/index.ts",
|
"./appearance": "./src/blocks/appearance/index.ts",
|
||||||
|
"./appearance/locales": "./src/blocks/appearance/locales/catalogs.ts",
|
||||||
|
"./appearance/locales/*": "./src/blocks/appearance/locales/*.ts",
|
||||||
"./chats": "./src/blocks/chats/index.ts",
|
"./chats": "./src/blocks/chats/index.ts",
|
||||||
|
"./chats/locales": "./src/blocks/chats/locales/catalogs.ts",
|
||||||
|
"./chats/locales/*": "./src/blocks/chats/locales/*.ts",
|
||||||
"./components/*": "./src/components/*.tsx",
|
"./components/*": "./src/components/*.tsx",
|
||||||
"./hooks/*": "./src/hooks/*.ts",
|
"./hooks/*": "./src/hooks/*.ts",
|
||||||
"./lib/*": "./src/lib/*.tsx",
|
"./lib/*": "./src/lib/*.tsx",
|
||||||
"./layout": "./src/blocks/layout/index.ts",
|
"./layout": "./src/blocks/layout/index.ts",
|
||||||
|
"./layout/locales": "./src/blocks/layout/locales/catalogs.ts",
|
||||||
|
"./layout/locales/*": "./src/blocks/layout/locales/*.ts",
|
||||||
"./navigation": "./src/blocks/navigation/index.ts",
|
"./navigation": "./src/blocks/navigation/index.ts",
|
||||||
"./notifications": "./src/blocks/notifications/index.ts"
|
"./navigation/locales": "./src/blocks/navigation/locales/catalogs.ts",
|
||||||
|
"./navigation/locales/*": "./src/blocks/navigation/locales/*.ts",
|
||||||
|
"./notifications": "./src/blocks/notifications/index.ts",
|
||||||
|
"./notifications/locales": "./src/blocks/notifications/locales/catalogs.ts",
|
||||||
|
"./notifications/locales/*": "./src/blocks/notifications/locales/*.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import { useHotkey } from "@tanstack/react-hotkeys"
|
import { useHotkey } from "@tanstack/react-hotkeys"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { useResolvedTheme, useUiState } from "./ui-state"
|
import { useResolvedTheme, useUiState } from "./ui-state"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -15,6 +16,62 @@ import {
|
|||||||
import { appearanceDialogHandle } from "./dialog"
|
import { appearanceDialogHandle } from "./dialog"
|
||||||
import { CompactPreference, ModePreference } from "./preferences"
|
import { CompactPreference, ModePreference } from "./preferences"
|
||||||
import { ThemeConfigPanel } from "./theme-config-panel"
|
import { ThemeConfigPanel } from "./theme-config-panel"
|
||||||
|
import { LocalizedText } from "../../components/localized-text"
|
||||||
|
import { appearanceMessages } from "./messages"
|
||||||
|
|
||||||
|
interface AppearanceHotkeysProps {
|
||||||
|
onToggleDialog: VoidFunction
|
||||||
|
onToggleTheme: VoidFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppearanceHotkeys({
|
||||||
|
onToggleDialog,
|
||||||
|
onToggleTheme,
|
||||||
|
}: AppearanceHotkeysProps) {
|
||||||
|
const appearanceTitle = useMessage(appearanceMessages.appearance)
|
||||||
|
const appearanceDescription = useMessage(
|
||||||
|
appearanceMessages.appearanceCommandDescription
|
||||||
|
)
|
||||||
|
const toggleThemeTitle = useMessage(appearanceMessages.toggleTheme)
|
||||||
|
const toggleThemeDescription = useMessage(
|
||||||
|
appearanceMessages.toggleThemeDescription
|
||||||
|
)
|
||||||
|
|
||||||
|
useHotkey("Mod+,", onToggleDialog, {
|
||||||
|
ignoreInputs: true,
|
||||||
|
preventDefault: true,
|
||||||
|
stopPropagation: false,
|
||||||
|
meta: {
|
||||||
|
name: appearanceTitle,
|
||||||
|
description: appearanceDescription,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
useHotkey("Mod+D", onToggleTheme, {
|
||||||
|
ignoreInputs: true,
|
||||||
|
preventDefault: true,
|
||||||
|
stopPropagation: false,
|
||||||
|
meta: {
|
||||||
|
name: toggleThemeTitle,
|
||||||
|
description: toggleThemeDescription,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppearanceDialogHeader() {
|
||||||
|
return (
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
<LocalizedText message={appearanceMessages.appearance} />
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<LocalizedText message={appearanceMessages.themeAndLayoutPreferences} />
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AppearanceController() {
|
export function AppearanceController() {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
@@ -33,37 +90,18 @@ export function AppearanceController() {
|
|||||||
setThemeMode(resolvedTheme === "dark" ? "light" : "dark")
|
setThemeMode(resolvedTheme === "dark" ? "light" : "dark")
|
||||||
}, [resolvedTheme, setThemeMode])
|
}, [resolvedTheme, setThemeMode])
|
||||||
|
|
||||||
useHotkey("Mod+,", toggleDialog, {
|
|
||||||
ignoreInputs: true,
|
|
||||||
preventDefault: true,
|
|
||||||
stopPropagation: false,
|
|
||||||
meta: {
|
|
||||||
name: "界面外观",
|
|
||||||
description: "打开主题与布局偏好",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
useHotkey("Mod+D", toggleTheme, {
|
|
||||||
ignoreInputs: true,
|
|
||||||
preventDefault: true,
|
|
||||||
stopPropagation: false,
|
|
||||||
meta: {
|
|
||||||
name: "切换主题",
|
|
||||||
description: "在浅色与深色主题之间切换",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog handle={appearanceDialogHandle} onOpenChange={setIsOpen}>
|
<Dialog handle={appearanceDialogHandle} onOpenChange={setIsOpen}>
|
||||||
|
<AppearanceHotkeys
|
||||||
|
onToggleDialog={toggleDialog}
|
||||||
|
onToggleTheme={toggleTheme}
|
||||||
|
/>
|
||||||
<NavigationActionTarget
|
<NavigationActionTarget
|
||||||
action={navigationActions.appearance}
|
action={navigationActions.appearance}
|
||||||
onInvoke={() => appearanceDialogHandle.open(null)}
|
onInvoke={() => appearanceDialogHandle.open(null)}
|
||||||
/>
|
/>
|
||||||
<DialogContent className="sm:max-w-max">
|
<DialogContent className="sm:max-w-max">
|
||||||
<DialogHeader>
|
<AppearanceDialogHeader />
|
||||||
<DialogTitle>界面外观</DialogTitle>
|
|
||||||
<DialogDescription>主题与布局偏好</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="-mx-6 max-h-[75vh] space-y-10 overflow-y-auto px-6">
|
<div className="-mx-6 max-h-[75vh] space-y-10 overflow-y-auto px-6">
|
||||||
<div className="flex flex-col gap-6 lg:flex-row">
|
<div className="flex flex-col gap-6 lg:flex-row">
|
||||||
<ModePreference />
|
<ModePreference />
|
||||||
@@ -73,13 +111,8 @@ export function AppearanceController() {
|
|||||||
<ThemeConfigPanel
|
<ThemeConfigPanel
|
||||||
scheme="light"
|
scheme="light"
|
||||||
active={resolvedTheme === "light"}
|
active={resolvedTheme === "light"}
|
||||||
description="当系统设置为浅色模式时,将使用此主题"
|
|
||||||
/>
|
|
||||||
<ThemeConfigPanel
|
|
||||||
scheme="dark"
|
|
||||||
active={resolvedTheme === "dark"}
|
|
||||||
description="当系统设置为深色模式时,将使用此主题"
|
|
||||||
/>
|
/>
|
||||||
|
<ThemeConfigPanel scheme="dark" active={resolvedTheme === "dark"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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 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,
|
||||||
|
"zh-Hans": zhHans,
|
||||||
|
"zh-Hant": zhHant,
|
||||||
|
},
|
||||||
|
locales: appearanceCatalogLocales,
|
||||||
|
messageIds: [
|
||||||
|
...Object.values(appearanceMessages),
|
||||||
|
...Object.values(themeColorMessages),
|
||||||
|
].map((descriptor) => descriptor.id),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { appearanceMessages, themeColorMessages } from "../messages"
|
||||||
|
import {
|
||||||
|
blockCatalogLocales,
|
||||||
|
type BlockCatalogLocale,
|
||||||
|
type BlockMessageCatalog,
|
||||||
|
} from "../../../i18n/catalogs"
|
||||||
|
|
||||||
|
export { blockCatalogLocales as appearanceCatalogLocales }
|
||||||
|
export type AppearanceCatalogLocale = BlockCatalogLocale
|
||||||
|
export type AppearanceMessageCatalog = BlockMessageCatalog<
|
||||||
|
typeof appearanceMessages & typeof themeColorMessages
|
||||||
|
>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
|
import { appearanceMessages, themeColorMessages } from "../messages.ts"
|
||||||
|
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "en"
|
||||||
|
export const languageTag = "en-US"
|
||||||
|
|
||||||
|
function createSourceCatalog(
|
||||||
|
...groups: readonly Record<string, MessageDescriptor>[]
|
||||||
|
): AppearanceMessageCatalog {
|
||||||
|
return Object.fromEntries(
|
||||||
|
groups.flatMap((group) =>
|
||||||
|
Object.values(group).map((descriptor) => [
|
||||||
|
descriptor.id,
|
||||||
|
descriptor.message,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
) as AppearanceMessageCatalog
|
||||||
|
}
|
||||||
|
|
||||||
|
export const messages = createSourceCatalog(
|
||||||
|
appearanceMessages,
|
||||||
|
themeColorMessages
|
||||||
|
) satisfies AppearanceMessageCatalog
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { AppearanceMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "zh-Hans"
|
||||||
|
export const languageTag = "zh-CN"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
|
export const appearanceMessages = {
|
||||||
|
active: /* i18n */ {
|
||||||
|
id: "blocks.appearance.active",
|
||||||
|
message: "Active",
|
||||||
|
},
|
||||||
|
appearance: /* i18n */ {
|
||||||
|
id: "blocks.appearance.title",
|
||||||
|
message: "Appearance",
|
||||||
|
},
|
||||||
|
appearanceCommandDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.command.description",
|
||||||
|
message: "Open theme and layout preferences",
|
||||||
|
},
|
||||||
|
baseColor: /* i18n */ {
|
||||||
|
id: "blocks.appearance.baseColor",
|
||||||
|
message: "Base color",
|
||||||
|
},
|
||||||
|
compactDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.compact.description",
|
||||||
|
message: "Use a compact width for the main page content",
|
||||||
|
},
|
||||||
|
compactLayout: /* i18n */ {
|
||||||
|
id: "blocks.appearance.compact.title",
|
||||||
|
message: "Compact layout",
|
||||||
|
},
|
||||||
|
darkMode: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.dark",
|
||||||
|
message: "Dark",
|
||||||
|
},
|
||||||
|
darkModeDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.dark.description",
|
||||||
|
message: "The interface will always use the dark theme",
|
||||||
|
},
|
||||||
|
darkTheme: /* i18n */ {
|
||||||
|
id: "blocks.appearance.theme.dark",
|
||||||
|
message: "Dark theme",
|
||||||
|
},
|
||||||
|
darkThemeDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.theme.dark.description",
|
||||||
|
message: "This theme is used when the system is in dark mode",
|
||||||
|
},
|
||||||
|
followSystem: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.system",
|
||||||
|
message: "System",
|
||||||
|
},
|
||||||
|
followSystemDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.system.description",
|
||||||
|
message: "The interface theme will follow the system appearance",
|
||||||
|
},
|
||||||
|
lightMode: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.light",
|
||||||
|
message: "Light",
|
||||||
|
},
|
||||||
|
lightModeDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.light.description",
|
||||||
|
message: "The interface will always use the light theme",
|
||||||
|
},
|
||||||
|
lightTheme: /* i18n */ {
|
||||||
|
id: "blocks.appearance.theme.light",
|
||||||
|
message: "Light theme",
|
||||||
|
},
|
||||||
|
lightThemeDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.theme.light.description",
|
||||||
|
message: "This theme is used when the system is in light mode",
|
||||||
|
},
|
||||||
|
primaryColor: /* i18n */ {
|
||||||
|
id: "blocks.appearance.primaryColor",
|
||||||
|
message: "Primary color",
|
||||||
|
},
|
||||||
|
themeAndLayoutPreferences: /* i18n */ {
|
||||||
|
id: "blocks.appearance.description",
|
||||||
|
message: "Theme and layout preferences",
|
||||||
|
},
|
||||||
|
themeMode: /* i18n */ {
|
||||||
|
id: "blocks.appearance.mode.title",
|
||||||
|
message: "Theme mode",
|
||||||
|
},
|
||||||
|
toggleTheme: /* i18n */ {
|
||||||
|
id: "blocks.appearance.toggleTheme",
|
||||||
|
message: "Toggle theme",
|
||||||
|
},
|
||||||
|
toggleThemeDescription: /* i18n */ {
|
||||||
|
id: "blocks.appearance.toggleTheme.description",
|
||||||
|
message: "Switch between light and dark themes",
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, MessageDescriptor>
|
||||||
|
|
||||||
|
export const themeColorMessages = {
|
||||||
|
amber: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.amber",
|
||||||
|
message: "Amber",
|
||||||
|
},
|
||||||
|
blue: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.blue",
|
||||||
|
message: "Blue",
|
||||||
|
},
|
||||||
|
cyan: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.cyan",
|
||||||
|
message: "Cyan",
|
||||||
|
},
|
||||||
|
emerald: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.emerald",
|
||||||
|
message: "Emerald",
|
||||||
|
},
|
||||||
|
fuchsia: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.fuchsia",
|
||||||
|
message: "Fuchsia",
|
||||||
|
},
|
||||||
|
green: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.green",
|
||||||
|
message: "Green",
|
||||||
|
},
|
||||||
|
indigo: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.indigo",
|
||||||
|
message: "Indigo",
|
||||||
|
},
|
||||||
|
lime: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.lime",
|
||||||
|
message: "Lime",
|
||||||
|
},
|
||||||
|
mauve: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.mauve",
|
||||||
|
message: "Mauve",
|
||||||
|
},
|
||||||
|
mist: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.mist",
|
||||||
|
message: "Mist",
|
||||||
|
},
|
||||||
|
neutral: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.neutral",
|
||||||
|
message: "Neutral",
|
||||||
|
},
|
||||||
|
olive: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.olive",
|
||||||
|
message: "Olive",
|
||||||
|
},
|
||||||
|
orange: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.orange",
|
||||||
|
message: "Orange",
|
||||||
|
},
|
||||||
|
pink: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.pink",
|
||||||
|
message: "Pink",
|
||||||
|
},
|
||||||
|
purple: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.purple",
|
||||||
|
message: "Purple",
|
||||||
|
},
|
||||||
|
red: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.red",
|
||||||
|
message: "Red",
|
||||||
|
},
|
||||||
|
rose: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.rose",
|
||||||
|
message: "Rose",
|
||||||
|
},
|
||||||
|
sky: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.sky",
|
||||||
|
message: "Sky",
|
||||||
|
},
|
||||||
|
stone: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.stone",
|
||||||
|
message: "Stone",
|
||||||
|
},
|
||||||
|
taupe: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.taupe",
|
||||||
|
message: "Taupe",
|
||||||
|
},
|
||||||
|
teal: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.teal",
|
||||||
|
message: "Teal",
|
||||||
|
},
|
||||||
|
violet: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.violet",
|
||||||
|
message: "Violet",
|
||||||
|
},
|
||||||
|
yellow: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.yellow",
|
||||||
|
message: "Yellow",
|
||||||
|
},
|
||||||
|
zinc: /* i18n */ {
|
||||||
|
id: "blocks.appearance.color.zinc",
|
||||||
|
message: "Zinc",
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, MessageDescriptor>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"
|
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -10,12 +11,8 @@ import { Switch } from "@workspace/ui/components/switch"
|
|||||||
|
|
||||||
import type { ThemeMode } from "./state"
|
import type { ThemeMode } from "./state"
|
||||||
import { useUiState } from "./ui-state"
|
import { useUiState } from "./ui-state"
|
||||||
|
import { LocalizedText } from "../../components/localized-text"
|
||||||
const modeItems: ReadonlyArray<{ label: string; value: ThemeMode }> = [
|
import { appearanceMessages } from "./messages"
|
||||||
{ label: "使用浅色", value: "light" },
|
|
||||||
{ label: "使用深色", value: "dark" },
|
|
||||||
{ label: "跟随系统", value: "system" },
|
|
||||||
]
|
|
||||||
|
|
||||||
function ModeIcon({ mode }: { mode: ThemeMode | null }) {
|
function ModeIcon({ mode }: { mode: ThemeMode | null }) {
|
||||||
if (mode === "light") return <SunIcon />
|
if (mode === "light") return <SunIcon />
|
||||||
@@ -25,15 +22,29 @@ function ModeIcon({ mode }: { mode: ThemeMode | null }) {
|
|||||||
|
|
||||||
export function ModePreference() {
|
export function ModePreference() {
|
||||||
const [themeMode, setThemeMode] = useUiState("theme-mode")
|
const [themeMode, setThemeMode] = useUiState("theme-mode")
|
||||||
|
const lightLabel = useMessage(appearanceMessages.lightMode)
|
||||||
|
const darkLabel = useMessage(appearanceMessages.darkMode)
|
||||||
|
const systemLabel = useMessage(appearanceMessages.followSystem)
|
||||||
|
const lightDescription = useMessage(appearanceMessages.lightModeDescription)
|
||||||
|
const darkDescription = useMessage(appearanceMessages.darkModeDescription)
|
||||||
|
const systemDescription = useMessage(
|
||||||
|
appearanceMessages.followSystemDescription
|
||||||
|
)
|
||||||
|
const title = useMessage(appearanceMessages.themeMode)
|
||||||
|
const modeItems: ReadonlyArray<{ label: string; value: ThemeMode }> = [
|
||||||
|
{ label: lightLabel, value: "light" },
|
||||||
|
{ label: darkLabel, value: "dark" },
|
||||||
|
{ label: systemLabel, value: "system" },
|
||||||
|
]
|
||||||
const description = {
|
const description = {
|
||||||
light: "界面将始终使用浅色主题",
|
light: lightDescription,
|
||||||
dark: "界面将始终使用深色主题",
|
dark: darkDescription,
|
||||||
system: "界面主题将跟随系统外观设置",
|
system: systemDescription,
|
||||||
}[themeMode]
|
}[themeMode]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:flex-1">
|
<div className="lg:flex-1">
|
||||||
<h2 className="font-semibold">主题模式</h2>
|
<h2 className="font-semibold">{title}</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||||
<Select
|
<Select
|
||||||
items={modeItems}
|
items={modeItems}
|
||||||
@@ -47,9 +58,7 @@ export function ModePreference() {
|
|||||||
{(value: ThemeMode | null) => (
|
{(value: ThemeMode | null) => (
|
||||||
<>
|
<>
|
||||||
<ModeIcon mode={value} />
|
<ModeIcon mode={value} />
|
||||||
{modeItems
|
{modeItems.find((item) => item.value === value)?.label}
|
||||||
.find((item) => item.value === value)
|
|
||||||
?.label.slice(-2)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SelectValue>
|
</SelectValue>
|
||||||
@@ -72,9 +81,11 @@ export function CompactPreference() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:flex-1">
|
<div className="lg:flex-1">
|
||||||
<h2 className="font-semibold">紧凑布局</h2>
|
<h2 className="font-semibold">
|
||||||
|
<LocalizedText message={appearanceMessages.compactLayout} />
|
||||||
|
</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
启用后,页面主内容将使用紧凑宽度
|
<LocalizedText message={appearanceMessages.compactDescription} />
|
||||||
</p>
|
</p>
|
||||||
<Switch
|
<Switch
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { CSSProperties } from "react"
|
import type { CSSProperties, ReactNode } from "react"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { cn } from "@workspace/ui/lib/utils"
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
import { themeColorMessages } from "./messages"
|
||||||
import {
|
import {
|
||||||
accentColors,
|
accentColors,
|
||||||
baseColors,
|
baseColors,
|
||||||
@@ -12,7 +14,6 @@ type ThemeColor = BaseColor | AccentColor
|
|||||||
|
|
||||||
type ThemeColorSwatch<T extends ThemeColor> = {
|
type ThemeColorSwatch<T extends ThemeColor> = {
|
||||||
name: T
|
name: T
|
||||||
title: string
|
|
||||||
light: {
|
light: {
|
||||||
background: string
|
background: string
|
||||||
foreground: string
|
foreground: string
|
||||||
@@ -31,7 +32,6 @@ function createSwatches<T extends ThemeColor>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
title: theme.title,
|
|
||||||
light: {
|
light: {
|
||||||
background: theme.cssVars.light.primary,
|
background: theme.cssVars.light.primary,
|
||||||
foreground: theme.cssVars.light["primary-foreground"],
|
foreground: theme.cssVars.light["primary-foreground"],
|
||||||
@@ -54,7 +54,7 @@ export function ThemeColorPicker<T extends ThemeColor>({
|
|||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
swatches: readonly ThemeColorSwatch<T>[]
|
swatches: readonly ThemeColorSwatch<T>[]
|
||||||
title: string
|
title: ReactNode
|
||||||
value: T
|
value: T
|
||||||
onChange: (color: T) => void
|
onChange: (color: T) => void
|
||||||
}) {
|
}) {
|
||||||
@@ -86,6 +86,7 @@ function ThemeColorButton({
|
|||||||
active: boolean
|
active: boolean
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const swatchTitle = useMessage(themeColorMessages[swatch.name])
|
||||||
const style = {
|
const style = {
|
||||||
"--light-bg": swatch.light.background,
|
"--light-bg": swatch.light.background,
|
||||||
"--light-fg": swatch.light.foreground,
|
"--light-fg": swatch.light.foreground,
|
||||||
@@ -96,7 +97,7 @@ function ThemeColorButton({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={swatch.title}
|
aria-label={swatchTitle}
|
||||||
aria-pressed={active}
|
aria-pressed={active}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-8 min-w-8 appearance-none truncate overflow-hidden rounded-4xl border-2 px-2 text-xs whitespace-nowrap ring-popover ring-inset",
|
"h-8 min-w-8 appearance-none truncate overflow-hidden rounded-4xl border-2 px-2 text-xs whitespace-nowrap ring-popover ring-inset",
|
||||||
@@ -108,7 +109,7 @@ function ThemeColorButton({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{swatch.title}
|
{swatchTitle}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,26 @@ import {
|
|||||||
} from "./theme-color-picker"
|
} from "./theme-color-picker"
|
||||||
import { ThemePreview } from "./theme-preview"
|
import { ThemePreview } from "./theme-preview"
|
||||||
import type { ThemeScheme } from "./theme"
|
import type { ThemeScheme } from "./theme"
|
||||||
|
import { LocalizedText } from "../../components/localized-text"
|
||||||
|
import { appearanceMessages } from "./messages"
|
||||||
|
|
||||||
export function ThemeConfigPanel({
|
export function ThemeConfigPanel({
|
||||||
scheme,
|
scheme,
|
||||||
active,
|
active,
|
||||||
description,
|
|
||||||
}: {
|
}: {
|
||||||
scheme: ThemeScheme
|
scheme: ThemeScheme
|
||||||
active: boolean
|
active: boolean
|
||||||
description: string
|
|
||||||
}) {
|
}) {
|
||||||
const stateKey = scheme === "light" ? "theme-light" : "theme-dark"
|
const stateKey = scheme === "light" ? "theme-light" : "theme-dark"
|
||||||
const [config, setConfig] = useUiState(stateKey)
|
const [config, setConfig] = useUiState(stateKey)
|
||||||
|
const titleMessage =
|
||||||
|
scheme === "light"
|
||||||
|
? appearanceMessages.lightTheme
|
||||||
|
: appearanceMessages.darkTheme
|
||||||
|
const descriptionMessage =
|
||||||
|
scheme === "light"
|
||||||
|
? appearanceMessages.lightThemeDescription
|
||||||
|
: appearanceMessages.darkThemeDescription
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-md rounded-xl border">
|
<div className="w-md rounded-xl border">
|
||||||
@@ -29,15 +37,23 @@ export function ThemeConfigPanel({
|
|||||||
) : (
|
) : (
|
||||||
<MoonIcon className="size-5" />
|
<MoonIcon className="size-5" />
|
||||||
)}
|
)}
|
||||||
<span>{scheme === "light" ? "浅色主题" : "深色主题"}</span>
|
<span>
|
||||||
{active && <Badge className="ml-auto">使用中</Badge>}
|
<LocalizedText message={titleMessage} />
|
||||||
|
</span>
|
||||||
|
{active && (
|
||||||
|
<Badge className="ml-auto">
|
||||||
|
<LocalizedText message={appearanceMessages.active} />
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 p-4">
|
<div className="space-y-4 p-4">
|
||||||
<p>{description}</p>
|
<p>
|
||||||
|
<LocalizedText message={descriptionMessage} />
|
||||||
|
</p>
|
||||||
<ThemePreview scheme={scheme} config={config} />
|
<ThemePreview scheme={scheme} config={config} />
|
||||||
<ThemeColorPicker
|
<ThemeColorPicker
|
||||||
swatches={baseColorSwatches}
|
swatches={baseColorSwatches}
|
||||||
title="基础颜色"
|
title={<LocalizedText message={appearanceMessages.baseColor} />}
|
||||||
value={config.baseColor}
|
value={config.baseColor}
|
||||||
onChange={(baseColor) =>
|
onChange={(baseColor) =>
|
||||||
setConfig((current) => ({ ...current, baseColor }))
|
setConfig((current) => ({ ...current, baseColor }))
|
||||||
@@ -45,7 +61,7 @@ export function ThemeConfigPanel({
|
|||||||
/>
|
/>
|
||||||
<ThemeColorPicker
|
<ThemeColorPicker
|
||||||
swatches={accentColorSwatches}
|
swatches={accentColorSwatches}
|
||||||
title="主要颜色"
|
title={<LocalizedText message={appearanceMessages.primaryColor} />}
|
||||||
value={config.accentColor}
|
value={config.accentColor}
|
||||||
onChange={(accentColor) =>
|
onChange={(accentColor) =>
|
||||||
setConfig((current) => ({ ...current, accentColor }))
|
setConfig((current) => ({ ...current, accentColor }))
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe("appearance themes", () => {
|
|||||||
|
|
||||||
expect(themes.map((theme) => theme.name)).toEqual(selectableColors)
|
expect(themes.map((theme) => theme.name)).toEqual(selectableColors)
|
||||||
expect(new Set(selectableColors).size).toBe(selectableColors.length)
|
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)
|
expect(themes.every((theme) => theme.title.length > 0)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export type Theme = {
|
|||||||
export const themes: Theme[] = [
|
export const themes: Theme[] = [
|
||||||
{
|
{
|
||||||
name: "neutral",
|
name: "neutral",
|
||||||
title: "中性灰",
|
title: "Neutral",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -142,7 +142,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "stone",
|
name: "stone",
|
||||||
title: "石灰",
|
title: "Stone",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -215,7 +215,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "zinc",
|
name: "zinc",
|
||||||
title: "锌灰",
|
title: "Zinc",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -288,7 +288,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mauve",
|
name: "mauve",
|
||||||
title: "紫灰",
|
title: "Mauve",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -361,7 +361,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "olive",
|
name: "olive",
|
||||||
title: "橄榄灰",
|
title: "Olive",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -434,7 +434,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mist",
|
name: "mist",
|
||||||
title: "雾灰",
|
title: "Mist",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -507,7 +507,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "taupe",
|
name: "taupe",
|
||||||
title: "灰褐",
|
title: "Taupe",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
background: "oklch(1 0 0)",
|
background: "oklch(1 0 0)",
|
||||||
@@ -580,7 +580,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amber",
|
name: "amber",
|
||||||
title: "琥珀色",
|
title: "Amber",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.555 0.163 48.998)",
|
primary: "oklch(0.555 0.163 48.998)",
|
||||||
@@ -612,7 +612,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "blue",
|
name: "blue",
|
||||||
title: "蓝色",
|
title: "Blue",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.488 0.243 264.376)",
|
primary: "oklch(0.488 0.243 264.376)",
|
||||||
@@ -644,7 +644,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cyan",
|
name: "cyan",
|
||||||
title: "青色",
|
title: "Cyan",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.52 0.105 223.128)",
|
primary: "oklch(0.52 0.105 223.128)",
|
||||||
@@ -676,7 +676,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "emerald",
|
name: "emerald",
|
||||||
title: "翠绿色",
|
title: "Emerald",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.508 0.118 165.612)",
|
primary: "oklch(0.508 0.118 165.612)",
|
||||||
@@ -708,7 +708,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fuchsia",
|
name: "fuchsia",
|
||||||
title: "紫红色",
|
title: "Fuchsia",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.518 0.253 323.949)",
|
primary: "oklch(0.518 0.253 323.949)",
|
||||||
@@ -740,7 +740,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "green",
|
name: "green",
|
||||||
title: "绿色",
|
title: "Green",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.527 0.154 150.069)",
|
primary: "oklch(0.527 0.154 150.069)",
|
||||||
@@ -772,7 +772,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "indigo",
|
name: "indigo",
|
||||||
title: "靛蓝色",
|
title: "Indigo",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.457 0.24 277.023)",
|
primary: "oklch(0.457 0.24 277.023)",
|
||||||
@@ -804,7 +804,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "lime",
|
name: "lime",
|
||||||
title: "青柠色",
|
title: "Lime",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.841 0.238 128.85)",
|
primary: "oklch(0.841 0.238 128.85)",
|
||||||
@@ -836,7 +836,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "orange",
|
name: "orange",
|
||||||
title: "橙色",
|
title: "Orange",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.553 0.195 38.402)",
|
primary: "oklch(0.553 0.195 38.402)",
|
||||||
@@ -868,7 +868,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pink",
|
name: "pink",
|
||||||
title: "粉色",
|
title: "Pink",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.525 0.223 3.958)",
|
primary: "oklch(0.525 0.223 3.958)",
|
||||||
@@ -900,7 +900,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "purple",
|
name: "purple",
|
||||||
title: "紫色",
|
title: "Purple",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.496 0.265 301.924)",
|
primary: "oklch(0.496 0.265 301.924)",
|
||||||
@@ -932,7 +932,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "red",
|
name: "red",
|
||||||
title: "红色",
|
title: "Red",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.505 0.213 27.518)",
|
primary: "oklch(0.505 0.213 27.518)",
|
||||||
@@ -964,7 +964,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rose",
|
name: "rose",
|
||||||
title: "玫红色",
|
title: "Rose",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.514 0.222 16.935)",
|
primary: "oklch(0.514 0.222 16.935)",
|
||||||
@@ -997,7 +997,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sky",
|
name: "sky",
|
||||||
title: "天蓝色",
|
title: "Sky",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.5 0.134 242.749)",
|
primary: "oklch(0.5 0.134 242.749)",
|
||||||
@@ -1029,7 +1029,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "teal",
|
name: "teal",
|
||||||
title: "蓝绿色",
|
title: "Teal",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.511 0.096 186.391)",
|
primary: "oklch(0.511 0.096 186.391)",
|
||||||
@@ -1061,7 +1061,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "violet",
|
name: "violet",
|
||||||
title: "紫罗兰色",
|
title: "Violet",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.491 0.27 292.581)",
|
primary: "oklch(0.491 0.27 292.581)",
|
||||||
@@ -1093,7 +1093,7 @@ export const themes: Theme[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "yellow",
|
name: "yellow",
|
||||||
title: "黄色",
|
title: "Yellow",
|
||||||
cssVars: {
|
cssVars: {
|
||||||
light: {
|
light: {
|
||||||
primary: "oklch(0.852 0.199 91.936)",
|
primary: "oklch(0.852 0.199 91.936)",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { ItemGroup } from "@workspace/ui/components/item"
|
import { ItemGroup } from "@workspace/ui/components/item"
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
} from "@workspace/ui/components/popover"
|
} from "@workspace/ui/components/popover"
|
||||||
|
|
||||||
import { useOnScroll } from "../../hooks/use-on-scroll"
|
import { useOnScroll } from "../../hooks/use-on-scroll"
|
||||||
|
import { chatMessages } from "./messages"
|
||||||
|
|
||||||
import { chatPopoverHandle } from "./chat-popover-handle"
|
import { chatPopoverHandle } from "./chat-popover-handle"
|
||||||
import { ChatThreadItem } from "./chat-thread-item"
|
import { ChatThreadItem } from "./chat-thread-item"
|
||||||
@@ -18,7 +20,9 @@ export interface ChatPopoverProps {
|
|||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatPopover({ threads, title = "Chats" }: ChatPopoverProps) {
|
export function ChatPopover({ threads, title }: ChatPopoverProps) {
|
||||||
|
const defaultTitle = useMessage(chatMessages.title)
|
||||||
|
|
||||||
useOnScroll(() => {
|
useOnScroll(() => {
|
||||||
chatPopoverHandle.close()
|
chatPopoverHandle.close()
|
||||||
})
|
})
|
||||||
@@ -28,7 +32,7 @@ export function ChatPopover({ threads, title = "Chats" }: ChatPopoverProps) {
|
|||||||
<PopoverContent className="h-120 p-2" alignOffset={-50} showArrow>
|
<PopoverContent className="h-120 p-2" alignOffset={-50} showArrow>
|
||||||
<PopoverHeader className="px-4 pt-4">
|
<PopoverHeader className="px-4 pt-4">
|
||||||
<PopoverTitle className="text-lg">
|
<PopoverTitle className="text-lg">
|
||||||
{title}({threads.length})
|
{title ?? defaultTitle}({threads.length})
|
||||||
</PopoverTitle>
|
</PopoverTitle>
|
||||||
<PopoverDescription />
|
<PopoverDescription />
|
||||||
</PopoverHeader>
|
</PopoverHeader>
|
||||||
|
|||||||
@@ -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
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<typeof chatMessages>
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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<string, MessageDescriptor>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
@@ -21,8 +22,13 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@workspace/ui/components/dropdown-menu"
|
} 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 { Icon } from "../../components/icon"
|
||||||
|
import { layoutMessages } from "./messages"
|
||||||
|
|
||||||
const MAX_VISIBLE_ENTRIES = 3
|
const MAX_VISIBLE_ENTRIES = 3
|
||||||
|
|
||||||
@@ -185,7 +191,7 @@ function BreadcrumbEntryContent({ entry }: { entry: BreadcrumbEntry }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{entry.icon && <Icon data={entry.icon} className="size-4 shrink-0" />}
|
{entry.icon && <Icon data={entry.icon} className="size-4 shrink-0" />}
|
||||||
{entry.label}
|
<NavigationLabel label={entry.label} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -303,11 +309,13 @@ function BreadcrumbRouteLink({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
|
function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
|
||||||
|
const menuLabel = useMessage(layoutMessages.breadcrumbMenuLabel)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
render={
|
render={
|
||||||
<Button aria-label="打开面包屑菜单" size="icon-sm" variant="ghost" />
|
<Button aria-label={menuLabel} size="icon-sm" variant="ghost" />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<BreadcrumbEllipsis />
|
<BreadcrumbEllipsis />
|
||||||
@@ -320,7 +328,7 @@ function BreadcrumbMenu({ entries }: { entries: readonly BreadcrumbEntry[] }) {
|
|||||||
{entry.icon && (
|
{entry.icon && (
|
||||||
<Icon data={entry.icon} className="size-4 shrink-0" />
|
<Icon data={entry.icon} className="size-4 shrink-0" />
|
||||||
)}
|
)}
|
||||||
{entry.label}
|
<NavigationLabel label={entry.label} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -405,7 +413,9 @@ export function AppBreadcrumb() {
|
|||||||
className="size-4 shrink-0"
|
className="size-4 shrink-0"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className="truncate">{item.entry.label}</span>
|
<span className="truncate">
|
||||||
|
<NavigationLabel label={item.entry.label} />
|
||||||
|
</span>
|
||||||
</BreadcrumbPage>
|
</BreadcrumbPage>
|
||||||
) : (
|
) : (
|
||||||
<BreadcrumbRouteLink
|
<BreadcrumbRouteLink
|
||||||
@@ -432,7 +442,7 @@ export function AppBreadcrumb() {
|
|||||||
{entry.icon && (
|
{entry.icon && (
|
||||||
<Icon data={entry.icon} className="size-4 shrink-0" />
|
<Icon data={entry.icon} className="size-4 shrink-0" />
|
||||||
)}
|
)}
|
||||||
{entry.label}
|
<NavigationLabel label={entry.label} />
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<span data-breadcrumb-measure-menu>
|
<span data-breadcrumb-measure-menu>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { AppQueryIndicator } from "./app-query-indicator"
|
|||||||
export interface AppLayoutProps {
|
export interface AppLayoutProps {
|
||||||
chatThreads: readonly ChatThread[]
|
chatThreads: readonly ChatThread[]
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
headerActions?: React.ReactNode
|
||||||
navigationGroups: readonly NavigationGroup[]
|
navigationGroups: readonly NavigationGroup[]
|
||||||
notifications: UseNotificationsOptions
|
notifications: UseNotificationsOptions
|
||||||
}
|
}
|
||||||
@@ -45,6 +46,7 @@ export interface AppLayoutProps {
|
|||||||
export function AppLayout({
|
export function AppLayout({
|
||||||
chatThreads,
|
chatThreads,
|
||||||
children,
|
children,
|
||||||
|
headerActions,
|
||||||
navigationGroups,
|
navigationGroups,
|
||||||
notifications,
|
notifications,
|
||||||
}: AppLayoutProps) {
|
}: AppLayoutProps) {
|
||||||
@@ -55,7 +57,7 @@ export function AppLayout({
|
|||||||
<NotificationCenterSheet {...notifications} />
|
<NotificationCenterSheet {...notifications} />
|
||||||
<ChatPopover threads={chatThreads} />
|
<ChatPopover threads={chatThreads} />
|
||||||
<div className="isolate min-h-svh w-svw">
|
<div className="isolate min-h-svh w-svw">
|
||||||
<AppHeader />
|
<AppHeader actions={headerActions} />
|
||||||
<div className="relative z-1 flex min-h-svh w-svw items-start">
|
<div className="relative z-1 flex min-h-svh w-svw items-start">
|
||||||
<AppHeaderBackground />
|
<AppHeaderBackground />
|
||||||
<AppSidebar groups={navigationGroups} />
|
<AppSidebar groups={navigationGroups} />
|
||||||
@@ -66,7 +68,7 @@ export function AppLayout({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppHeader() {
|
function AppHeader({ actions }: { actions?: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-3 h-0">
|
<header className="sticky top-0 z-3 h-0">
|
||||||
<div className="flex h-(--ui-header-height) items-center">
|
<div className="flex h-(--ui-header-height) items-center">
|
||||||
@@ -157,6 +159,7 @@ function AppHeader() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||||
<GlobalSearchTrigger />
|
<GlobalSearchTrigger />
|
||||||
|
{actions}
|
||||||
<NotificationCenterTrigger
|
<NotificationCenterTrigger
|
||||||
render={<HeaderActionButton icon={BellIcon} showIndicator />}
|
render={<HeaderActionButton icon={BellIcon} showIndicator />}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { useRefresh } from "../../hooks/use-refresh"
|
import { useRefresh } from "../../hooks/use-refresh"
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -7,9 +8,12 @@ import {
|
|||||||
import { HeaderActionButton } from "./header-action-button"
|
import { HeaderActionButton } from "./header-action-button"
|
||||||
import { ReloadIcon } from "@hugeicons/core-free-icons"
|
import { ReloadIcon } from "@hugeicons/core-free-icons"
|
||||||
import { cn } from "@workspace/ui/lib/utils"
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
import { layoutMessages } from "./messages"
|
||||||
|
|
||||||
export function AppQueryIndicator({ className }: { className?: string }) {
|
export function AppQueryIndicator({ className }: { className?: string }) {
|
||||||
const { canRefresh, isRefreshing, refresh } = useRefresh()
|
const { canRefresh, isRefreshing, refresh } = useRefresh()
|
||||||
|
const refreshLabel = useMessage(layoutMessages.refreshLabel)
|
||||||
|
const refreshDescription = useMessage(layoutMessages.refreshDescription)
|
||||||
|
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +33,7 @@ export function AppQueryIndicator({ className }: { className?: string }) {
|
|||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
render={
|
render={
|
||||||
<HeaderActionButton
|
<HeaderActionButton
|
||||||
aria-label="刷新当前页面数据"
|
aria-label={refreshLabel}
|
||||||
className={cn("[&_svg]:size-4", className)}
|
className={cn("[&_svg]:size-4", className)}
|
||||||
disabled={!canRefresh}
|
disabled={!canRefresh}
|
||||||
icon={ReloadIcon}
|
icon={ReloadIcon}
|
||||||
@@ -37,11 +41,7 @@ export function AppQueryIndicator({ className }: { className?: string }) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TooltipContent showArrow>
|
<TooltipContent showArrow>{refreshDescription}</TooltipContent>
|
||||||
点击可以刷新
|
|
||||||
<br />
|
|
||||||
当前页面数据
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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<typeof layoutMessages>
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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<string, MessageDescriptor>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { formatForDisplay } from "@tanstack/react-hotkeys"
|
import { formatForDisplay } from "@tanstack/react-hotkeys"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { Button, type ButtonProps } from "@workspace/ui/components/button"
|
import { Button, type ButtonProps } from "@workspace/ui/components/button"
|
||||||
import {
|
import {
|
||||||
createDialogHandle,
|
createDialogHandle,
|
||||||
@@ -41,8 +42,10 @@ import {
|
|||||||
ShieldKeyIcon,
|
ShieldKeyIcon,
|
||||||
SwatchIcon,
|
SwatchIcon,
|
||||||
} from "@hugeicons/core-free-icons"
|
} from "@hugeicons/core-free-icons"
|
||||||
|
import { layoutMessages } from "./messages"
|
||||||
|
|
||||||
interface UserMenuItem {
|
interface UserMenuItem {
|
||||||
|
id: string
|
||||||
action?: NavigationAction
|
action?: NavigationAction
|
||||||
shortcut?: string
|
shortcut?: string
|
||||||
icon: IconData
|
icon: IconData
|
||||||
@@ -53,24 +56,6 @@ interface UserMenuTriggerProps {
|
|||||||
className?: string
|
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:
|
let userMenuHandles:
|
||||||
| {
|
| {
|
||||||
dialog: DialogHandle<void>
|
dialog: DialogHandle<void>
|
||||||
@@ -123,8 +108,11 @@ function UserMenuPopoverTrigger({ className }: UserMenuTriggerProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UserMenuTriggerButton({ className, ...props }: ButtonProps) {
|
function UserMenuTriggerButton({ className, ...props }: ButtonProps) {
|
||||||
|
const openUserMenuLabel = useMessage(layoutMessages.openUserMenu)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderActionButton
|
<HeaderActionButton
|
||||||
|
aria-label={openUserMenuLabel}
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-hidden after:pointer-events-none after:absolute after:inset-0 after:z-999 after:rounded-md after:border after:border-foreground/35",
|
"overflow-hidden after:pointer-events-none after:absolute after:inset-0 after:z-999 after:rounded-md after:border after:border-foreground/35",
|
||||||
className
|
className
|
||||||
@@ -140,6 +128,31 @@ function UserMenuTriggerButton({ className, ...props }: ButtonProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
|
function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
|
||||||
|
const appearanceLabel = useMessage(layoutMessages.appearance)
|
||||||
|
const keybindingsLabel = useMessage(layoutMessages.keybindings)
|
||||||
|
const accountPasswordLabel = useMessage(layoutMessages.accountPassword)
|
||||||
|
const logoutLabel = useMessage(layoutMessages.logout)
|
||||||
|
const userMenuItems: readonly UserMenuItem[] = [
|
||||||
|
{
|
||||||
|
id: "appearance",
|
||||||
|
action: navigationActions.appearance,
|
||||||
|
icon: SwatchIcon,
|
||||||
|
label: appearanceLabel,
|
||||||
|
shortcut: "Mod+,",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "keybindings",
|
||||||
|
icon: KeyboardIcon,
|
||||||
|
label: keybindingsLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "account-password",
|
||||||
|
icon: ShieldKeyIcon,
|
||||||
|
label: accountPasswordLabel,
|
||||||
|
shortcut: "Mod+P",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-72">
|
<div className="w-72">
|
||||||
<div className="flex flex-col items-center pt-10 pb-6">
|
<div className="flex flex-col items-center pt-10 pb-6">
|
||||||
@@ -153,7 +166,7 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
|
|||||||
<div className="border-t border-dashed border-border p-2">
|
<div className="border-t border-dashed border-border p-2">
|
||||||
<ul className="space-y">
|
<ul className="space-y">
|
||||||
{userMenuItems.map((item) => (
|
{userMenuItems.map((item) => (
|
||||||
<li key={item.label}>
|
<li key={item.id}>
|
||||||
<Button
|
<Button
|
||||||
className="w-full justify-start font-normal"
|
className="w-full justify-start font-normal"
|
||||||
size="lg"
|
size="lg"
|
||||||
@@ -176,7 +189,7 @@ function UserMenuContent({ onItemSelect }: { onItemSelect: VoidFunction }) {
|
|||||||
<div className="rounded-b-md border-t p-2">
|
<div className="rounded-b-md border-t p-2">
|
||||||
<Button className="w-full font-normal" variant="destructive" size="lg">
|
<Button className="w-full font-normal" variant="destructive" size="lg">
|
||||||
<Icon data={LogoutCircle02Icon} size="sm" />
|
<Icon data={LogoutCircle02Icon} size="sm" />
|
||||||
退出登录
|
{logoutLabel}
|
||||||
<MenuShortcutSequence shortcuts={["Q", "Q", "Q"]} />
|
<MenuShortcutSequence shortcuts={["Q", "Q", "Q"]} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -213,12 +226,14 @@ function UserMenuDialogTrigger({ className }: UserMenuTriggerProps) {
|
|||||||
|
|
||||||
function UserMenuDialog() {
|
function UserMenuDialog() {
|
||||||
const { dialog } = getOrCreateUserMenuHandles()
|
const { dialog } = getOrCreateUserMenuHandles()
|
||||||
|
const title = useMessage(layoutMessages.userMenuTitle)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog handle={dialog}>
|
<Dialog handle={dialog}>
|
||||||
<DialogContent className="w-fit p-0">
|
<DialogContent className="w-fit p-0">
|
||||||
<DialogHeader className="sr-only">
|
<DialogHeader className="sr-only">
|
||||||
<DialogTitle></DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogDescription></DialogDescription>
|
<DialogDescription>{title}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<UserMenuContent onItemSelect={() => dialog.close()} />
|
<UserMenuContent onItemSelect={() => dialog.close()} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Icon } from "../../components/icon"
|
|||||||
|
|
||||||
import { resolveNavigationItemIcon } from "./item-icon"
|
import { resolveNavigationItemIcon } from "./item-icon"
|
||||||
import { NavigationBadge, NavigationLink } from "./navigation-button"
|
import { NavigationBadge, NavigationLink } from "./navigation-button"
|
||||||
|
import { NavigationLabel } from "./navigation-label"
|
||||||
import type { GetNavigationRouteState } from "./route-state"
|
import type { GetNavigationRouteState } from "./route-state"
|
||||||
import type { NavigationItem } from "./types"
|
import type { NavigationItem } from "./types"
|
||||||
|
|
||||||
@@ -37,7 +38,9 @@ export function SidebarFlyoutMenuItems({
|
|||||||
className="h-9 gap-3 px-3 py-1 data-route-active:bg-primary/10 data-route-active:text-primary! data-route-active:**:text-primary!"
|
className="h-9 gap-3 px-3 py-1 data-route-active:bg-primary/10 data-route-active:text-primary! data-route-active:**:text-primary!"
|
||||||
>
|
>
|
||||||
{iconElement}
|
{iconElement}
|
||||||
<span className="min-w-0 flex-1 truncate">{item.label}</span>
|
<span className="min-w-0 flex-1 truncate">
|
||||||
|
<NavigationLabel label={item.label} />
|
||||||
|
</span>
|
||||||
<NavigationBadge className="ms-auto" itemId={item.id} />
|
<NavigationBadge className="ms-auto" itemId={item.id} />
|
||||||
</DropdownMenuSubTrigger>
|
</DropdownMenuSubTrigger>
|
||||||
<DropdownMenuSubContent className="grid w-fit max-w-(--available-width) min-w-44 gap-0.5">
|
<DropdownMenuSubContent className="grid w-fit max-w-(--available-width) min-w-44 gap-0.5">
|
||||||
@@ -59,7 +62,9 @@ export function SidebarFlyoutMenuItems({
|
|||||||
render={item.to ? <NavigationLink to={item.to} /> : undefined}
|
render={item.to ? <NavigationLink to={item.to} /> : undefined}
|
||||||
>
|
>
|
||||||
{iconElement}
|
{iconElement}
|
||||||
<span className="min-w-0 flex-1 truncate">{item.label}</span>
|
<span className="min-w-0 flex-1 truncate">
|
||||||
|
<NavigationLabel label={item.label} />
|
||||||
|
</span>
|
||||||
<NavigationBadge className="ms-auto" itemId={item.id} />
|
<NavigationBadge className="ms-auto" itemId={item.id} />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export {
|
|||||||
} from "./mobile-navigation"
|
} from "./mobile-navigation"
|
||||||
export { resolveNavigationItemIcon } from "./item-icon"
|
export { resolveNavigationItemIcon } from "./item-icon"
|
||||||
export { PrimaryNavigation } from "./primary-navigation"
|
export { PrimaryNavigation } from "./primary-navigation"
|
||||||
|
export { NavigationLabel, TruncatedNavigationLabel } from "./navigation-label"
|
||||||
export { resolveNavigationRouteState } from "./route-state"
|
export { resolveNavigationRouteState } from "./route-state"
|
||||||
export {
|
export {
|
||||||
NavigationProvider,
|
NavigationProvider,
|
||||||
@@ -16,4 +17,8 @@ export type {
|
|||||||
NavigationRouteState,
|
NavigationRouteState,
|
||||||
} from "./route-state"
|
} from "./route-state"
|
||||||
export type { NavigationInfo, NavigationKey } from "./use-navigation"
|
export type { NavigationInfo, NavigationKey } from "./use-navigation"
|
||||||
export type { NavigationGroup, NavigationItem } from "./types"
|
export type {
|
||||||
|
NavigationGroup,
|
||||||
|
NavigationItem,
|
||||||
|
NavigationLabelValue,
|
||||||
|
} from "./types"
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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 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,
|
||||||
|
"zh-Hans": zhHans,
|
||||||
|
"zh-Hant": zhHant,
|
||||||
|
},
|
||||||
|
locales: navigationCatalogLocales,
|
||||||
|
messageIds: Object.values(navigationMessages).map(
|
||||||
|
(descriptor) => descriptor.id
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { navigationMessages } from "../messages"
|
||||||
|
import {
|
||||||
|
blockCatalogLocales,
|
||||||
|
type BlockCatalogLocale,
|
||||||
|
type BlockMessageCatalog,
|
||||||
|
} from "../../../i18n/catalogs"
|
||||||
|
|
||||||
|
export { blockCatalogLocales as navigationCatalogLocales }
|
||||||
|
export type NavigationCatalogLocale = BlockCatalogLocale
|
||||||
|
export type NavigationMessageCatalog = BlockMessageCatalog<
|
||||||
|
typeof navigationMessages
|
||||||
|
>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { NavigationMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "en"
|
||||||
|
export const languageTag = "en-US"
|
||||||
|
export const messages = {
|
||||||
|
"blocks.navigation.description": "Navigate through the app.",
|
||||||
|
"blocks.navigation.open": "Open navigation",
|
||||||
|
"blocks.navigation.title": "Navigation",
|
||||||
|
} as const satisfies NavigationMessageCatalog
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { NavigationMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "zh-Hans"
|
||||||
|
export const languageTag = "zh-CN"
|
||||||
|
export const messages = {
|
||||||
|
"blocks.navigation.description": "浏览应用中的页面。",
|
||||||
|
"blocks.navigation.open": "打开导航",
|
||||||
|
"blocks.navigation.title": "导航",
|
||||||
|
} as const satisfies NavigationMessageCatalog
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
|
export const navigationMessages = {
|
||||||
|
description: /* i18n */ {
|
||||||
|
id: "blocks.navigation.description",
|
||||||
|
message: "Navigate through the app.",
|
||||||
|
},
|
||||||
|
open: /* i18n */ {
|
||||||
|
id: "blocks.navigation.open",
|
||||||
|
message: "Open navigation",
|
||||||
|
},
|
||||||
|
title: /* i18n */ {
|
||||||
|
id: "blocks.navigation.title",
|
||||||
|
message: "Navigation",
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, MessageDescriptor>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useLocation } from "@tanstack/react-router"
|
import { useLocation } from "@tanstack/react-router"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import type { ButtonProps } from "@workspace/ui/components/button"
|
import type { ButtonProps } from "@workspace/ui/components/button"
|
||||||
import { Separator } from "@workspace/ui/components/separator"
|
import { Separator } from "@workspace/ui/components/separator"
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +16,8 @@ import { cn, separate } from "@workspace/ui/lib/utils"
|
|||||||
import { ChevronRightIcon } from "lucide-react"
|
import { ChevronRightIcon } from "lucide-react"
|
||||||
|
|
||||||
import { HeaderActionButton } from "../layout/header-action-button"
|
import { HeaderActionButton } from "../layout/header-action-button"
|
||||||
|
import { navigationMessages } from "./messages"
|
||||||
|
import { NavigationLabel } from "./navigation-label"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NavigationBadge,
|
NavigationBadge,
|
||||||
@@ -37,11 +40,15 @@ export function MobileNavigationSheetTrigger({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
|
const openNavigationLabel = useMessage(navigationMessages.open)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SheetTrigger
|
<SheetTrigger
|
||||||
handle={mobileNavigationSheetHandle}
|
handle={mobileNavigationSheetHandle}
|
||||||
className={cn("-ml-2 hidden mobile:inline-flex", className)}
|
className={cn("-ml-2 hidden mobile:inline-flex", className)}
|
||||||
render={<HeaderActionButton aria-label="打开导航" {...props} />}
|
render={
|
||||||
|
<HeaderActionButton aria-label={openNavigationLabel} {...props} />
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
data={Menu02Icon}
|
data={Menu02Icon}
|
||||||
@@ -57,6 +64,9 @@ export interface MobileNavigationSheetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MobileNavigationSheet({ groups }: MobileNavigationSheetProps) {
|
export function MobileNavigationSheet({ groups }: MobileNavigationSheetProps) {
|
||||||
|
const title = useMessage(navigationMessages.title)
|
||||||
|
const description = useMessage(navigationMessages.description)
|
||||||
|
|
||||||
useOnBreakpoint((breakpoint) => {
|
useOnBreakpoint((breakpoint) => {
|
||||||
if (breakpoint !== "mobile" && mobileNavigationSheetHandle.isOpen) {
|
if (breakpoint !== "mobile" && mobileNavigationSheetHandle.isOpen) {
|
||||||
mobileNavigationSheetHandle.close()
|
mobileNavigationSheetHandle.close()
|
||||||
@@ -75,8 +85,8 @@ export function MobileNavigationSheet({ groups }: MobileNavigationSheetProps) {
|
|||||||
<Sheet handle={mobileNavigationSheetHandle}>
|
<Sheet handle={mobileNavigationSheetHandle}>
|
||||||
<SheetContent className="max-w-72 gap-0 bg-popover/90" side="left">
|
<SheetContent className="max-w-72 gap-0 bg-popover/90" side="left">
|
||||||
<SheetHeader className="text-left">
|
<SheetHeader className="text-left">
|
||||||
<SheetTitle>Navigation</SheetTitle>
|
<SheetTitle>{title}</SheetTitle>
|
||||||
<SheetDescription>Navigate through the app.</SheetDescription>
|
<SheetDescription>{description}</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -124,7 +134,7 @@ function FlatNavigationGroupList({
|
|||||||
{items.map((group) => (
|
{items.map((group) => (
|
||||||
<div key={group.id}>
|
<div key={group.id}>
|
||||||
<div className="mx-3 mb-2 truncate text-xs whitespace-nowrap tablet:hidden not-mobile:collapsed:hidden">
|
<div className="mx-3 mb-2 truncate text-xs whitespace-nowrap tablet:hidden not-mobile:collapsed:hidden">
|
||||||
{group.label}
|
<NavigationLabel label={group.label} />
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md ring ring-sidebar-border">
|
<div className="rounded-md ring ring-sidebar-border">
|
||||||
{separate({
|
{separate({
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { cn } from "@workspace/ui/lib/utils"
|
|||||||
import { ChevronRightIcon } from "lucide-react"
|
import { ChevronRightIcon } from "lucide-react"
|
||||||
|
|
||||||
import { Icon, type IconData } from "../../components/icon"
|
import { Icon, type IconData } from "../../components/icon"
|
||||||
|
import { NavigationLabel, TruncatedNavigationLabel } from "./navigation-label"
|
||||||
|
import type { NavigationLabelValue } from "./types"
|
||||||
|
|
||||||
export interface NavigationButtonProps extends Omit<
|
export interface NavigationButtonProps extends Omit<
|
||||||
useRender.ComponentProps<"button", {}>,
|
useRender.ComponentProps<"button", {}>,
|
||||||
@@ -16,7 +18,7 @@ export interface NavigationButtonProps extends Omit<
|
|||||||
icon?: IconData
|
icon?: IconData
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
isCurrent: boolean
|
isCurrent: boolean
|
||||||
label: string
|
label: NavigationLabelValue
|
||||||
layout?: "inline" | "stacked" | "responsive"
|
layout?: "inline" | "stacked" | "responsive"
|
||||||
className?: string | (() => string | undefined) | undefined
|
className?: string | (() => string | undefined) | undefined
|
||||||
style?:
|
style?:
|
||||||
@@ -105,21 +107,22 @@ export function NavigationButton({
|
|||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 origin-top truncate whitespace-nowrap",
|
"min-w-0 flex-1 origin-top overflow-hidden whitespace-nowrap",
|
||||||
layout === "stacked"
|
layout === "stacked"
|
||||||
? [
|
? [
|
||||||
"col-start-1 col-end-5 row-start-2 justify-self-center",
|
"col-start-1 col-end-5 row-start-2 w-full max-w-full",
|
||||||
"scale-85 text-center text-xs",
|
"scale-85 justify-center text-xs",
|
||||||
]
|
]
|
||||||
: layout === "responsive"
|
: layout === "responsive"
|
||||||
? [
|
? [
|
||||||
"tablet:col-start-1 tablet:col-end-5 tablet:row-start-2",
|
"tablet:col-start-1 tablet:col-end-5 tablet:row-start-2",
|
||||||
"tablet:justify-self-center tablet:text-center tablet:text-xs",
|
"tablet:w-full tablet:max-w-full tablet:justify-center tablet:text-xs",
|
||||||
"not-mobile:collapsed:col-start-1",
|
"not-mobile:collapsed:col-start-1",
|
||||||
"not-mobile:collapsed:col-end-5",
|
"not-mobile:collapsed:col-end-5",
|
||||||
"not-mobile:collapsed:row-start-2",
|
"not-mobile:collapsed:row-start-2",
|
||||||
"not-mobile:collapsed:justify-self-center",
|
"not-mobile:collapsed:w-full",
|
||||||
"not-mobile:collapsed:text-center",
|
"not-mobile:collapsed:max-w-full",
|
||||||
|
"not-mobile:collapsed:justify-center",
|
||||||
"not-mobile:collapsed:text-xs",
|
"not-mobile:collapsed:text-xs",
|
||||||
"tablet:scale-85",
|
"tablet:scale-85",
|
||||||
"not-mobile:collapsed:scale-85",
|
"not-mobile:collapsed:scale-85",
|
||||||
@@ -127,7 +130,11 @@ export function NavigationButton({
|
|||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{layout === "inline" ? (
|
||||||
|
<NavigationLabel label={label} />
|
||||||
|
) : (
|
||||||
|
<TruncatedNavigationLabel label={label} />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
{hasTrailingContent && (
|
{hasTrailingContent && (
|
||||||
<span
|
<span
|
||||||
@@ -158,7 +165,7 @@ export function NavigationButton({
|
|||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
"aria-label": label,
|
"aria-label": typeof label === "string" ? label : undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
return useRender({
|
return useRender({
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
|
||||||
|
import { LocalizedText } from "../../components/localized-text"
|
||||||
|
import type { NavigationLabelValue } from "./types"
|
||||||
|
|
||||||
|
export interface NavigationLabelProps {
|
||||||
|
label: NavigationLabelValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a navigation label without subscribing the surrounding navigation
|
||||||
|
* item to locale changes.
|
||||||
|
*/
|
||||||
|
export function NavigationLabel({ label }: NavigationLabelProps) {
|
||||||
|
if (typeof label === "string") {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LocalizedText message={label} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TruncatedNavigationLabelProps extends NavigationLabelProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centers labels that fit while keeping overflowing labels anchored to the
|
||||||
|
* inline start edge and truncating their inline end.
|
||||||
|
*/
|
||||||
|
export function TruncatedNavigationLabel({
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
}: TruncatedNavigationLabelProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-block max-w-full truncate text-start align-bottom",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<NavigationLabel label={label} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
type NavigationButtonProps,
|
type NavigationButtonProps,
|
||||||
} from "./navigation-button"
|
} from "./navigation-button"
|
||||||
import { InlineNestedNavigationList } from "./nested-navigation"
|
import { InlineNestedNavigationList } from "./nested-navigation"
|
||||||
|
import { NavigationLabel } from "./navigation-label"
|
||||||
import type { GetNavigationRouteState } from "./route-state"
|
import type { GetNavigationRouteState } from "./route-state"
|
||||||
import type { NavigationGroup, NavigationItem } from "./types"
|
import type { NavigationGroup, NavigationItem } from "./types"
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ export function PrimaryNavigation({
|
|||||||
{items.map((group) => (
|
{items.map((group) => (
|
||||||
<div key={group.id}>
|
<div key={group.id}>
|
||||||
<div className="mx-3 mb-2 truncate text-xs whitespace-nowrap tablet:hidden not-mobile:collapsed:hidden">
|
<div className="mx-3 mb-2 truncate text-xs whitespace-nowrap tablet:hidden not-mobile:collapsed:hidden">
|
||||||
{group.label}
|
<NavigationLabel label={group.label} />
|
||||||
</div>
|
</div>
|
||||||
<div className="tablet:space-y-1 not-mobile:collapsed:space-y-1">
|
<div className="tablet:space-y-1 not-mobile:collapsed:space-y-1">
|
||||||
{group.items.map((item) => (
|
{group.items.map((item) => (
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
import type { IconData } from "../../components/icon"
|
import type { IconData } from "../../components/icon"
|
||||||
|
|
||||||
|
export type NavigationLabelValue = string | MessageDescriptor
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
id: string
|
id: string
|
||||||
to?: string
|
to?: string
|
||||||
label: string
|
label: NavigationLabelValue
|
||||||
icon?: IconData
|
icon?: IconData
|
||||||
showIcon?: boolean
|
showIcon?: boolean
|
||||||
items?: readonly NavigationItem[]
|
items?: readonly NavigationItem[]
|
||||||
@@ -11,7 +15,7 @@ export interface NavigationItem {
|
|||||||
|
|
||||||
export interface NavigationGroup {
|
export interface NavigationGroup {
|
||||||
id: string
|
id: string
|
||||||
label: string
|
label: NavigationLabelValue
|
||||||
showIcon?: boolean
|
showIcon?: boolean
|
||||||
items: readonly NavigationItem[]
|
items: readonly NavigationItem[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import * as React from "react"
|
|||||||
|
|
||||||
import type { IconData } from "../../components/icon"
|
import type { IconData } from "../../components/icon"
|
||||||
import { resolveNavigationItemIcon } from "./item-icon"
|
import { resolveNavigationItemIcon } from "./item-icon"
|
||||||
import type { NavigationGroup, NavigationItem } from "./types"
|
import type {
|
||||||
|
NavigationGroup,
|
||||||
|
NavigationItem,
|
||||||
|
NavigationLabelValue,
|
||||||
|
} from "./types"
|
||||||
|
|
||||||
export type NavigationKey = NavigationItem["id"]
|
export type NavigationKey = NavigationItem["id"]
|
||||||
|
|
||||||
@@ -11,7 +15,7 @@ export interface NavigationInfo {
|
|||||||
id: NavigationKey
|
id: NavigationKey
|
||||||
icon?: IconData
|
icon?: IconData
|
||||||
items?: readonly NavigationInfo[]
|
items?: readonly NavigationInfo[]
|
||||||
label: string
|
label: NavigationLabelValue
|
||||||
to?: string
|
to?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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 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,
|
||||||
|
"zh-Hans": zhHans,
|
||||||
|
"zh-Hant": zhHant,
|
||||||
|
},
|
||||||
|
locales: notificationCatalogLocales,
|
||||||
|
messageIds: Object.values(notificationMessages).map(
|
||||||
|
(descriptor) => descriptor.id
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { notificationMessages } from "../messages"
|
||||||
|
import {
|
||||||
|
blockCatalogLocales,
|
||||||
|
type BlockCatalogLocale,
|
||||||
|
type BlockMessageCatalog,
|
||||||
|
} from "../../../i18n/catalogs"
|
||||||
|
|
||||||
|
export { blockCatalogLocales as notificationCatalogLocales }
|
||||||
|
export type NotificationCatalogLocale = BlockCatalogLocale
|
||||||
|
export type NotificationMessageCatalog = BlockMessageCatalog<
|
||||||
|
typeof notificationMessages
|
||||||
|
>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { NotificationMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "en"
|
||||||
|
export const languageTag = "en-US"
|
||||||
|
export const messages = {
|
||||||
|
"blocks.notifications.description": "View and manage notifications",
|
||||||
|
"blocks.notifications.empty.description": "New activity will appear here.",
|
||||||
|
"blocks.notifications.empty.title": "No notifications",
|
||||||
|
"blocks.notifications.error.description":
|
||||||
|
"Check your network connection and try again.",
|
||||||
|
"blocks.notifications.error.title": "Unable to load notifications",
|
||||||
|
"blocks.notifications.justNow": "just now",
|
||||||
|
"blocks.notifications.loading": "Loading notifications…",
|
||||||
|
"blocks.notifications.markAllAsRead": "Mark all as read",
|
||||||
|
"blocks.notifications.retry": "Try again",
|
||||||
|
"blocks.notifications.title": "Notifications",
|
||||||
|
"blocks.notifications.viewAll": "View all",
|
||||||
|
} as const satisfies NotificationMessageCatalog
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { NotificationMessageCatalog } from "./catalogs"
|
||||||
|
|
||||||
|
export const locale = "zh-Hans"
|
||||||
|
export const languageTag = "zh-CN"
|
||||||
|
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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { MessageDescriptor } from "@workspace/i18n"
|
||||||
|
|
||||||
|
export const notificationMessages = {
|
||||||
|
description: /* i18n */ {
|
||||||
|
id: "blocks.notifications.description",
|
||||||
|
message: "View and manage notifications",
|
||||||
|
},
|
||||||
|
emptyDescription: /* i18n */ {
|
||||||
|
id: "blocks.notifications.empty.description",
|
||||||
|
message: "New activity will appear here.",
|
||||||
|
},
|
||||||
|
emptyTitle: /* i18n */ {
|
||||||
|
id: "blocks.notifications.empty.title",
|
||||||
|
message: "No notifications",
|
||||||
|
},
|
||||||
|
errorDescription: /* i18n */ {
|
||||||
|
id: "blocks.notifications.error.description",
|
||||||
|
message: "Check your network connection and try again.",
|
||||||
|
},
|
||||||
|
errorTitle: /* i18n */ {
|
||||||
|
id: "blocks.notifications.error.title",
|
||||||
|
message: "Unable to load notifications",
|
||||||
|
},
|
||||||
|
justNow: /* i18n */ {
|
||||||
|
id: "blocks.notifications.justNow",
|
||||||
|
message: "just now",
|
||||||
|
},
|
||||||
|
loading: /* i18n */ {
|
||||||
|
id: "blocks.notifications.loading",
|
||||||
|
message: "Loading notifications…",
|
||||||
|
},
|
||||||
|
markAllAsRead: /* i18n */ {
|
||||||
|
id: "blocks.notifications.markAllAsRead",
|
||||||
|
message: "Mark all as read",
|
||||||
|
},
|
||||||
|
retry: /* i18n */ {
|
||||||
|
id: "blocks.notifications.retry",
|
||||||
|
message: "Try again",
|
||||||
|
},
|
||||||
|
title: /* i18n */ {
|
||||||
|
id: "blocks.notifications.title",
|
||||||
|
message: "Notifications",
|
||||||
|
},
|
||||||
|
viewAll: /* i18n */ {
|
||||||
|
id: "blocks.notifications.viewAll",
|
||||||
|
message: "View all",
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, MessageDescriptor>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckCheckIcon } from "lucide-react"
|
import { CheckCheckIcon } from "lucide-react"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import { Button } from "@workspace/ui/components/button"
|
import { Button } from "@workspace/ui/components/button"
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
} from "@workspace/ui/components/tooltip"
|
} from "@workspace/ui/components/tooltip"
|
||||||
import { cn } from "@workspace/ui/lib/utils"
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
|
||||||
|
import { notificationMessages } from "./messages"
|
||||||
import { NotificationItem } from "./notification-item"
|
import { NotificationItem } from "./notification-item"
|
||||||
import type { Notification, NotificationActionHandler } from "./types"
|
import type { Notification, NotificationActionHandler } from "./types"
|
||||||
|
|
||||||
@@ -37,17 +39,30 @@ export interface NotificationCenterProps extends Omit<
|
|||||||
|
|
||||||
export function NotificationCenter({
|
export function NotificationCenter({
|
||||||
className,
|
className,
|
||||||
description = "查看并处理通知",
|
description,
|
||||||
notifications,
|
notifications,
|
||||||
onAction,
|
onAction,
|
||||||
onMarkAllAsRead,
|
onMarkAllAsRead,
|
||||||
onRefetch,
|
onRefetch,
|
||||||
onViewAll,
|
onViewAll,
|
||||||
status,
|
status,
|
||||||
title = "Notifications",
|
title,
|
||||||
viewAllLabel = "查看全部",
|
viewAllLabel,
|
||||||
...props
|
...props
|
||||||
}: NotificationCenterProps) {
|
}: NotificationCenterProps) {
|
||||||
|
const defaultDescription = useMessage(notificationMessages.description)
|
||||||
|
const defaultTitle = useMessage(notificationMessages.title)
|
||||||
|
const defaultViewAllLabel = useMessage(notificationMessages.viewAll)
|
||||||
|
const markAllAsReadLabel = useMessage(notificationMessages.markAllAsRead)
|
||||||
|
const loadingLabel = useMessage(notificationMessages.loading)
|
||||||
|
const errorTitle = useMessage(notificationMessages.errorTitle)
|
||||||
|
const errorDescription = useMessage(notificationMessages.errorDescription)
|
||||||
|
const retryLabel = useMessage(notificationMessages.retry)
|
||||||
|
const emptyTitle = useMessage(notificationMessages.emptyTitle)
|
||||||
|
const emptyDescription = useMessage(notificationMessages.emptyDescription)
|
||||||
|
const resolvedDescription = description ?? defaultDescription
|
||||||
|
const resolvedTitle = title ?? defaultTitle
|
||||||
|
const resolvedViewAllLabel = viewAllLabel ?? defaultViewAllLabel
|
||||||
const unreadCount = notifications.filter(
|
const unreadCount = notifications.filter(
|
||||||
(notification) => !notification.isRead
|
(notification) => !notification.isRead
|
||||||
).length
|
).length
|
||||||
@@ -59,14 +74,16 @@ export function NotificationCenter({
|
|||||||
>
|
>
|
||||||
<header className="grid grid-cols-[1fr_auto] p-4">
|
<header className="grid grid-cols-[1fr_auto] p-4">
|
||||||
<h2 className="order-1 font-heading font-medium text-foreground">
|
<h2 className="order-1 font-heading font-medium text-foreground">
|
||||||
{title}
|
{resolvedTitle}
|
||||||
{unreadCount > 0 && (
|
{unreadCount > 0 && (
|
||||||
<span className="ms-1.5 text-sm font-normal text-muted-foreground">
|
<span className="ms-1.5 text-sm font-normal text-muted-foreground">
|
||||||
({unreadCount})
|
({unreadCount})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
{description && <p className="sr-only">{description}</p>}
|
{resolvedDescription && (
|
||||||
|
<p className="sr-only">{resolvedDescription}</p>
|
||||||
|
)}
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
delay={0}
|
delay={0}
|
||||||
@@ -74,7 +91,7 @@ export function NotificationCenter({
|
|||||||
disabled={unreadCount === 0}
|
disabled={unreadCount === 0}
|
||||||
render={
|
render={
|
||||||
<Button
|
<Button
|
||||||
aria-label="全部标记为已读"
|
aria-label={markAllAsReadLabel}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +101,7 @@ export function NotificationCenter({
|
|||||||
<CheckCheckIcon />
|
<CheckCheckIcon />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent align="end" showArrow>
|
<TooltipContent align="end" showArrow>
|
||||||
全部标记为已读
|
{markAllAsReadLabel}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</header>
|
</header>
|
||||||
@@ -94,23 +111,23 @@ export function NotificationCenter({
|
|||||||
role="status"
|
role="status"
|
||||||
className="flex min-h-0 flex-1 items-center justify-center text-sm text-muted-foreground"
|
className="flex min-h-0 flex-1 items-center justify-center text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
正在加载通知…
|
{loadingLabel}
|
||||||
</div>
|
</div>
|
||||||
) : status === "error" ? (
|
) : status === "error" ? (
|
||||||
<Empty>
|
<Empty>
|
||||||
<EmptyContent>
|
<EmptyContent>
|
||||||
<EmptyTitle>无法加载通知</EmptyTitle>
|
<EmptyTitle>{errorTitle}</EmptyTitle>
|
||||||
<EmptyDescription>请检查网络连接后重试。</EmptyDescription>
|
<EmptyDescription>{errorDescription}</EmptyDescription>
|
||||||
<Button variant="outline" onClick={onRefetch}>
|
<Button variant="outline" onClick={onRefetch}>
|
||||||
重新加载
|
{retryLabel}
|
||||||
</Button>
|
</Button>
|
||||||
</EmptyContent>
|
</EmptyContent>
|
||||||
</Empty>
|
</Empty>
|
||||||
) : notifications.length === 0 ? (
|
) : notifications.length === 0 ? (
|
||||||
<Empty>
|
<Empty>
|
||||||
<EmptyContent>
|
<EmptyContent>
|
||||||
<EmptyTitle>暂无通知</EmptyTitle>
|
<EmptyTitle>{emptyTitle}</EmptyTitle>
|
||||||
<EmptyDescription>新的动态会显示在这里。</EmptyDescription>
|
<EmptyDescription>{emptyDescription}</EmptyDescription>
|
||||||
</EmptyContent>
|
</EmptyContent>
|
||||||
</Empty>
|
</Empty>
|
||||||
) : (
|
) : (
|
||||||
@@ -137,7 +154,7 @@ export function NotificationCenter({
|
|||||||
|
|
||||||
<footer className="mt-auto flex flex-col gap-2 p-4">
|
<footer className="mt-auto flex flex-col gap-2 p-4">
|
||||||
<Button size="lg" variant="ghost" className="h-12" onClick={onViewAll}>
|
<Button size="lg" variant="ghost" className="h-12" onClick={onViewAll}>
|
||||||
{viewAllLabel}
|
{resolvedViewAllLabel}
|
||||||
</Button>
|
</Button>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
|
import { useFormatters, useMessage } from "@workspace/i18n"
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
import { cn } from "@workspace/ui/lib/utils"
|
import { cn } from "@workspace/ui/lib/utils"
|
||||||
|
|
||||||
import { Icon } from "../../components/icon"
|
import { Icon } from "../../components/icon"
|
||||||
|
import { notificationMessages } from "./messages"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Notification,
|
Notification,
|
||||||
@@ -43,10 +45,6 @@ const ICON_TONE_CLASSES: Record<NotificationTone, string> = {
|
|||||||
warning: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
warning: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
||||||
}
|
}
|
||||||
|
|
||||||
const RELATIVE_TIME_FORMAT = new Intl.RelativeTimeFormat("zh-CN", {
|
|
||||||
numeric: "auto",
|
|
||||||
})
|
|
||||||
|
|
||||||
const RELATIVE_TIME_UNITS: ReadonlyArray<
|
const RELATIVE_TIME_UNITS: ReadonlyArray<
|
||||||
readonly [seconds: number, unit: Intl.RelativeTimeFormatUnit]
|
readonly [seconds: number, unit: Intl.RelativeTimeFormatUnit]
|
||||||
> = [
|
> = [
|
||||||
@@ -71,7 +69,11 @@ function getInitials(media: Extract<NotificationMedia, { kind: "avatar" }>) {
|
|||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRelativeTime(createdAt: string) {
|
function formatNotificationTime(
|
||||||
|
createdAt: string,
|
||||||
|
formatter: (value: number, unit: Intl.RelativeTimeFormatUnit) => string,
|
||||||
|
justNow: string
|
||||||
|
) {
|
||||||
const createdAtTime = new Date(createdAt).getTime()
|
const createdAtTime = new Date(createdAt).getTime()
|
||||||
|
|
||||||
if (!Number.isFinite(createdAtTime)) {
|
if (!Number.isFinite(createdAtTime)) {
|
||||||
@@ -83,14 +85,11 @@ function formatRelativeTime(createdAt: string) {
|
|||||||
|
|
||||||
for (const [seconds, unit] of RELATIVE_TIME_UNITS) {
|
for (const [seconds, unit] of RELATIVE_TIME_UNITS) {
|
||||||
if (absoluteDifference >= seconds) {
|
if (absoluteDifference >= seconds) {
|
||||||
return RELATIVE_TIME_FORMAT.format(
|
return formatter(Math.round(differenceInSeconds / seconds), unit)
|
||||||
Math.round(differenceInSeconds / seconds),
|
|
||||||
unit
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "刚刚"
|
return justNow
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationItemMedia({ media }: { media: NotificationMedia }) {
|
function NotificationItemMedia({ media }: { media: NotificationMedia }) {
|
||||||
@@ -148,6 +147,9 @@ export function NotificationItem({
|
|||||||
ref,
|
ref,
|
||||||
...props
|
...props
|
||||||
}: NotificationItemProps) {
|
}: NotificationItemProps) {
|
||||||
|
const { formatRelativeTime } = useFormatters()
|
||||||
|
const justNow = useMessage(notificationMessages.justNow)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseItem
|
<BaseItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -181,7 +183,12 @@ export function NotificationItem({
|
|||||||
)}
|
)}
|
||||||
<ItemDescription className="flex items-center gap-1">
|
<ItemDescription className="flex items-center gap-1">
|
||||||
<time dateTime={notification.createdAt}>
|
<time dateTime={notification.createdAt}>
|
||||||
{formatRelativeTime(notification.createdAt)}
|
{formatNotificationTime(
|
||||||
|
notification.createdAt,
|
||||||
|
(value, unit) =>
|
||||||
|
formatRelativeTime(value, unit, { numeric: "auto" }),
|
||||||
|
justNow
|
||||||
|
)}
|
||||||
</time>
|
</time>
|
||||||
{notification.category && (
|
{notification.category && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { useMessage } from "@workspace/i18n"
|
||||||
import {
|
import {
|
||||||
createSheetHandle,
|
createSheetHandle,
|
||||||
Sheet,
|
Sheet,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
} from "@workspace/ui/components/sheet"
|
} from "@workspace/ui/components/sheet"
|
||||||
|
|
||||||
|
import { notificationMessages } from "./messages"
|
||||||
import { NotificationCenter } from "./notification-center"
|
import { NotificationCenter } from "./notification-center"
|
||||||
import type { Notification, NotificationActionHandler } from "./types"
|
import type { Notification, NotificationActionHandler } from "./types"
|
||||||
import {
|
import {
|
||||||
@@ -41,9 +43,12 @@ export function NotificationCenterSheet({
|
|||||||
onViewAll,
|
onViewAll,
|
||||||
queryFn,
|
queryFn,
|
||||||
queryKey,
|
queryKey,
|
||||||
title = "Notifications",
|
title,
|
||||||
}: NotificationCenterSheetProps) {
|
}: NotificationCenterSheetProps) {
|
||||||
const notifications = useNotifications({ queryFn, queryKey })
|
const notifications = useNotifications({ queryFn, queryKey })
|
||||||
|
const defaultDescription = useMessage(notificationMessages.description)
|
||||||
|
const defaultTitle = useMessage(notificationMessages.title)
|
||||||
|
const resolvedTitle = title ?? defaultTitle
|
||||||
|
|
||||||
const handleAction = React.useCallback<NotificationActionHandler>(
|
const handleAction = React.useCallback<NotificationActionHandler>(
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -65,8 +70,10 @@ export function NotificationCenterSheet({
|
|||||||
className="w-105 gap-0 bg-popover/90"
|
className="w-105 gap-0 bg-popover/90"
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
>
|
>
|
||||||
<SheetTitle className="sr-only">{title}</SheetTitle>
|
<SheetTitle className="sr-only">{resolvedTitle}</SheetTitle>
|
||||||
<SheetDescription className="sr-only">查看并处理通知</SheetDescription>
|
<SheetDescription className="sr-only">
|
||||||
|
{defaultDescription}
|
||||||
|
</SheetDescription>
|
||||||
<NotificationCenter
|
<NotificationCenter
|
||||||
notifications={notifications.notifications}
|
notifications={notifications.notifications}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
@@ -74,7 +81,7 @@ export function NotificationCenterSheet({
|
|||||||
onRefetch={notifications.refetch}
|
onRefetch={notifications.refetch}
|
||||||
onViewAll={onViewAll}
|
onViewAll={onViewAll}
|
||||||
status={notifications.status}
|
status={notifications.status}
|
||||||
title={title}
|
title={resolvedTitle}
|
||||||
/>
|
/>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user