mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-26 17:09:14 +02:00
Compare commits
1 Commits
agent/lamb
...
fix/i18n-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e881f86309 |
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useRef } from "react";
|
||||
import { Paperclip } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "@multica/ui/lib/utils";
|
||||
|
||||
interface FileUploadButtonProps {
|
||||
@@ -18,7 +19,9 @@ function FileUploadButton({
|
||||
className,
|
||||
size = "default",
|
||||
}: FileUploadButtonProps) {
|
||||
const { t } = useTranslation("ui");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const attachLabel = t(($) => $.attach_file);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
@@ -36,8 +39,8 @@ function FileUploadButton({
|
||||
type="button"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
disabled={disabled}
|
||||
aria-label="Attach file"
|
||||
title="Attach file"
|
||||
aria-label={attachLabel}
|
||||
title={attachLabel}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground transition-colors disabled:opacity-50 disabled:pointer-events-none",
|
||||
btnSize,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { cn } from "@multica/ui/lib/utils"
|
||||
import { Button } from "@multica/ui/components/ui/button"
|
||||
@@ -67,9 +68,10 @@ function PaginationPrevious({
|
||||
text = "Previous",
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
|
||||
const { t } = useTranslation("ui")
|
||||
return (
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
aria-label={t(($) => $.pagination_previous)}
|
||||
size="default"
|
||||
className={cn("pl-1.5!", className)}
|
||||
{...props}
|
||||
@@ -85,9 +87,10 @@ function PaginationNext({
|
||||
text = "Next",
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
|
||||
const { t } = useTranslation("ui")
|
||||
return (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
aria-label={t(($) => $.pagination_next)}
|
||||
size="default"
|
||||
className={cn("pr-1.5!", className)}
|
||||
{...props}
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react"
|
||||
import { mergeProps } from "@base-ui/react/merge-props"
|
||||
import { useRender } from "@base-ui/react/use-render"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useIsMobile } from "@multica/ui/hooks/use-mobile"
|
||||
import { cn } from "@multica/ui/lib/utils"
|
||||
@@ -265,6 +266,7 @@ function SidebarTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Button>) {
|
||||
const { toggleSidebar } = useSidebar()
|
||||
const { t } = useTranslation("ui")
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -280,13 +282,15 @@ function SidebarTrigger({
|
||||
{...props}
|
||||
>
|
||||
<PanelLeftIcon />
|
||||
<span className="sr-only">Toggle Sidebar</span>
|
||||
<span className="sr-only">{t(($) => $.toggle_sidebar)}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
const { toggleSidebar, setWidth, setIsResizing } = useSidebar()
|
||||
const { t } = useTranslation("ui")
|
||||
const toggleLabel = t(($) => $.toggle_sidebar)
|
||||
const didDragRef = React.useRef(false)
|
||||
const dragRef = React.useRef<{ startX: number; startWidth: number } | null>(null)
|
||||
|
||||
@@ -330,11 +334,11 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
<button
|
||||
data-sidebar="rail"
|
||||
data-slot="sidebar-rail"
|
||||
aria-label="Toggle Sidebar"
|
||||
aria-label={toggleLabel}
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
onMouseDown={onMouseDown}
|
||||
title="Toggle Sidebar"
|
||||
title={toggleLabel}
|
||||
className={cn(
|
||||
"absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
|
||||
"in-data-[side=left]:cursor-col-resize in-data-[side=right]:cursor-col-resize",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react'
|
||||
import { codeToHtml, bundledLanguages, type BundledLanguage } from 'shiki'
|
||||
import { Copy, Check } from "lucide-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Button } from "@multica/ui/components/ui/button"
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@multica/ui/components/ui/tooltip"
|
||||
import { cn } from '@multica/ui/lib/utils'
|
||||
@@ -61,6 +62,7 @@ export function CodeBlock({
|
||||
className,
|
||||
mode = 'full'
|
||||
}: CodeBlockProps): React.JSX.Element {
|
||||
const { t } = useTranslation("ui")
|
||||
const [highlighted, setHighlighted] = React.useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
const [copied, setCopied] = React.useState(false)
|
||||
@@ -178,7 +180,7 @@ export function CodeBlock({
|
||||
{/* Language label + copy button */}
|
||||
<div className="flex items-center justify-between px-3 py-1.5 bg-muted/50 border-b text-xs">
|
||||
<span className="text-muted-foreground font-medium uppercase tracking-wide">
|
||||
{resolvedLang !== 'text' ? resolvedLang : 'plain text'}
|
||||
{resolvedLang !== 'text' ? resolvedLang : t(($) => $.plain_text)}
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
@@ -188,7 +190,7 @@ export function CodeBlock({
|
||||
size="icon-xs"
|
||||
onClick={handleCopy}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-foreground"
|
||||
aria-label="Copy code"
|
||||
aria-label={t(($) => $.copy_code)}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="size-3.5 text-success" />
|
||||
@@ -198,7 +200,7 @@ export function CodeBlock({
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>Copy code</TooltipContent>
|
||||
<TooltipContent>{t(($) => $.copy_code)}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"./hooks/*": "./hooks/*.ts",
|
||||
"./lib/utils": "./lib/utils.ts",
|
||||
"./lib/data-table": "./lib/data-table.ts",
|
||||
"./i18n-types": "./types/i18next.ts",
|
||||
"./styles/tokens.css": "./styles/tokens.css",
|
||||
"./styles/base.css": "./styles/base.css"
|
||||
},
|
||||
@@ -52,8 +53,10 @@
|
||||
"vaul": "^1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
"react-dom": "catalog:",
|
||||
"react-i18next": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@multica/tsconfig": "workspace:*",
|
||||
|
||||
35
packages/ui/types/i18next.ts
Normal file
35
packages/ui/types/i18next.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import "i18next";
|
||||
|
||||
// Local slice of the i18next augmentation that owns the `ui` namespace.
|
||||
// The base augmentation lives in packages/views/i18n/resources-types.ts and
|
||||
// declares everything else; this file contributes only the `ui` entry via
|
||||
// declaration merging on the global `I18nResources` interface so
|
||||
// packages/ui can typecheck the selector form standalone without depending
|
||||
// on @multica/views.
|
||||
//
|
||||
// When both files are loaded together (in a consumer's typecheck program),
|
||||
// the two augmentations compose: views contributes common/auth/... and ui
|
||||
// contributes `ui`. No properties overlap, so the merge is conflict-free.
|
||||
//
|
||||
// The resource shape is mirrored from packages/views/locales/{en,zh-Hans}/ui.json.
|
||||
// Drift between the JSON and these types is not caught by the locale parity
|
||||
// test — if you add a key to ui.json, mirror it here.
|
||||
declare global {
|
||||
interface I18nResources {
|
||||
ui: {
|
||||
attach_file: string;
|
||||
toggle_sidebar: string;
|
||||
pagination_previous: string;
|
||||
pagination_next: string;
|
||||
copy_code: string;
|
||||
plain_text: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module "i18next" {
|
||||
interface CustomTypeOptions {
|
||||
resources: I18nResources;
|
||||
enableSelector: true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import "i18next";
|
||||
// Pulls in the `ui` namespace augmentation owned by packages/ui — see
|
||||
// packages/ui/types/i18next.ts. Side-effect import is required for views'
|
||||
// typecheck program to see ui's contribution to `I18nResources`.
|
||||
import "@multica/ui/i18n-types";
|
||||
import type common from "../locales/en/common.json";
|
||||
import type auth from "../locales/en/auth.json";
|
||||
import type settings from "../locales/en/settings.json";
|
||||
@@ -30,33 +34,43 @@ import type usage from "../locales/en/usage.json";
|
||||
// Adding a namespace: drop a JSON file under en/ and zh-Hans/, then add
|
||||
// the matching `import type` + entry below. Type inference on `t($ => $)`
|
||||
// follows automatically.
|
||||
//
|
||||
// The resource shape lives on a global `I18nResources` interface (not a
|
||||
// type literal inside CustomTypeOptions) so other packages can contribute
|
||||
// namespaces via declaration merging. See packages/ui/types/i18next.d.ts —
|
||||
// it adds the `ui` namespace there, which lets packages/ui typecheck the
|
||||
// selector form standalone without depending on @multica/views.
|
||||
declare global {
|
||||
interface I18nResources {
|
||||
common: typeof common;
|
||||
auth: typeof auth;
|
||||
settings: typeof settings;
|
||||
issues: typeof issues;
|
||||
agents: typeof agents;
|
||||
editor: typeof editor;
|
||||
onboarding: typeof onboarding;
|
||||
invite: typeof invite;
|
||||
labels: typeof labels;
|
||||
members: typeof members;
|
||||
"my-issues": typeof myIssues;
|
||||
search: typeof search;
|
||||
inbox: typeof inbox;
|
||||
workspace: typeof workspace;
|
||||
projects: typeof projects;
|
||||
autopilots: typeof autopilots;
|
||||
skills: typeof skills;
|
||||
chat: typeof chat;
|
||||
modals: typeof modals;
|
||||
runtimes: typeof runtimes;
|
||||
layout: typeof layout;
|
||||
usage: typeof usage;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "i18next" {
|
||||
interface CustomTypeOptions {
|
||||
defaultNS: "common";
|
||||
resources: {
|
||||
common: typeof common;
|
||||
auth: typeof auth;
|
||||
settings: typeof settings;
|
||||
issues: typeof issues;
|
||||
agents: typeof agents;
|
||||
editor: typeof editor;
|
||||
onboarding: typeof onboarding;
|
||||
invite: typeof invite;
|
||||
labels: typeof labels;
|
||||
members: typeof members;
|
||||
"my-issues": typeof myIssues;
|
||||
search: typeof search;
|
||||
inbox: typeof inbox;
|
||||
workspace: typeof workspace;
|
||||
projects: typeof projects;
|
||||
autopilots: typeof autopilots;
|
||||
skills: typeof skills;
|
||||
chat: typeof chat;
|
||||
modals: typeof modals;
|
||||
runtimes: typeof runtimes;
|
||||
layout: typeof layout;
|
||||
usage: typeof usage;
|
||||
};
|
||||
resources: I18nResources;
|
||||
enableSelector: true;
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/views/locales/en/ui.json
Normal file
8
packages/views/locales/en/ui.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"attach_file": "Attach file",
|
||||
"toggle_sidebar": "Toggle Sidebar",
|
||||
"pagination_previous": "Go to previous page",
|
||||
"pagination_next": "Go to next page",
|
||||
"copy_code": "Copy code",
|
||||
"plain_text": "plain text"
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import enModals from "./en/modals.json";
|
||||
import enRuntimes from "./en/runtimes.json";
|
||||
import enLayout from "./en/layout.json";
|
||||
import enUsage from "./en/usage.json";
|
||||
import enUi from "./en/ui.json";
|
||||
import zhHansCommon from "./zh-Hans/common.json";
|
||||
import zhHansAuth from "./zh-Hans/auth.json";
|
||||
import zhHansSettings from "./zh-Hans/settings.json";
|
||||
@@ -43,6 +44,7 @@ import zhHansModals from "./zh-Hans/modals.json";
|
||||
import zhHansRuntimes from "./zh-Hans/runtimes.json";
|
||||
import zhHansLayout from "./zh-Hans/layout.json";
|
||||
import zhHansUsage from "./zh-Hans/usage.json";
|
||||
import zhHansUi from "./zh-Hans/ui.json";
|
||||
|
||||
// Single source of truth for the resource bundle. Both apps (web layout +
|
||||
// desktop App.tsx) import from here so adding a locale or namespace happens
|
||||
@@ -71,6 +73,7 @@ export const RESOURCES: Record<SupportedLocale, LocaleResources> = {
|
||||
runtimes: enRuntimes,
|
||||
layout: enLayout,
|
||||
usage: enUsage,
|
||||
ui: enUi,
|
||||
},
|
||||
"zh-Hans": {
|
||||
common: zhHansCommon,
|
||||
@@ -95,5 +98,6 @@ export const RESOURCES: Record<SupportedLocale, LocaleResources> = {
|
||||
runtimes: zhHansRuntimes,
|
||||
layout: zhHansLayout,
|
||||
usage: zhHansUsage,
|
||||
ui: zhHansUi,
|
||||
},
|
||||
};
|
||||
|
||||
8
packages/views/locales/zh-Hans/ui.json
Normal file
8
packages/views/locales/zh-Hans/ui.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"attach_file": "添加附件",
|
||||
"toggle_sidebar": "切换侧边栏",
|
||||
"pagination_previous": "上一页",
|
||||
"pagination_next": "下一页",
|
||||
"copy_code": "复制代码",
|
||||
"plain_text": "纯文本"
|
||||
}
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -591,6 +591,9 @@ importers:
|
||||
emoji-mart:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
i18next:
|
||||
specifier: 'catalog:'
|
||||
version: 26.0.8(typescript@5.9.3)
|
||||
input-otp:
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -615,6 +618,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 'catalog:'
|
||||
version: 19.2.3(react@19.2.3)
|
||||
react-i18next:
|
||||
specifier: 'catalog:'
|
||||
version: 17.0.6(i18next@26.0.8(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
|
||||
react-markdown:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.3)
|
||||
|
||||
Reference in New Issue
Block a user