diff --git a/.github/workflows/ci-i18n.yml b/.github/workflows/ci-i18n.yml index c0c1aa96f..a75122b48 100644 --- a/.github/workflows/ci-i18n.yml +++ b/.github/workflows/ci-i18n.yml @@ -15,7 +15,7 @@ permissions: jobs: i18n-audit: - runs-on: blacksmith-4vcpu-ubuntu-2204 + runs-on: ubuntu-22.04 steps: - name: Checkout diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 9b0cbde62..85c8c6bcc 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - os: [blacksmith-4vcpu-ubuntu-2204, macos-14] + os: [ubuntu-22.04, macos-14] steps: - name: Checkout diff --git a/apps/app/src/components/chat/new-conversation-starter.tsx b/apps/app/src/components/chat/new-conversation-starter.tsx index 2148e33a2..cc00cbedd 100644 --- a/apps/app/src/components/chat/new-conversation-starter.tsx +++ b/apps/app/src/components/chat/new-conversation-starter.tsx @@ -143,7 +143,7 @@ export type StarterCapability = { type TemplateCategory = "site" | "poster" | "cards" | "app" | "article" | "slides" | "report" | "other" | "video"; -const TEMPLATE_CATEGORY_ICONS: Record = { +const TEMPLATE_CATEGORY_ICONS: Record = { site: Globe2, poster: Image, cards: PanelsTopLeft, @@ -152,8 +152,26 @@ const TEMPLATE_CATEGORY_ICONS: Record = { slides: Presentation, report: FileChartColumnIncreasing, other: FolderOpen, - video: Film, -}; + video: Film, +}; + +const TEMPLATE_CATEGORY_LABEL_KEYS = { + site: "new_conversation.template_category.site", + poster: "new_conversation.template_category.poster", + cards: "new_conversation.template_category.cards", + app: "new_conversation.template_category.app", + article: "new_conversation.template_category.article", + slides: "new_conversation.template_category.slides", + report: "new_conversation.template_category.report", + other: "new_conversation.template_category.other", + video: "new_conversation.template_category.video", +} as const satisfies Record; + +const ANIMATION_UPDATE_LABEL_KEYS = { + live: "new_conversation.animations.update_live", + rebuild: "new_conversation.animations.update_rebuild", + reload: "new_conversation.animations.update_reload", +} as const; const MODE_ACTIONS: Record> = { work: [ @@ -238,7 +256,7 @@ function TemplateStrip({ const categoryTemplates = templates.filter((template) => ( template.manifest.category === category && (category !== "video" || template.manifest.surface === "video") )); - const categoryLabel = t(`new_conversation.template_category.${category}`); + const categoryLabel = t(TEMPLATE_CATEGORY_LABEL_KEYS[category]); const CategoryIcon = TEMPLATE_CATEGORY_ICONS[category]; const scrollerRef = useRef(null); const [scrollProgress, setScrollProgress] = useState(0); @@ -665,7 +683,7 @@ function AnimationParameterDialog({
- {lastUpdate ? t(`new_conversation.animations.update_${lastUpdate}`) : t("new_conversation.animations.preview_defaults")} + {lastUpdate ? t(ANIMATION_UPDATE_LABEL_KEYS[lastUpdate]) : t("new_conversation.animations.preview_defaults")} {t("new_conversation.animations.current_time", { time: preservedTimeRef.current.toFixed(1) })}
diff --git a/apps/app/src/components/template-catalog-dialog.tsx b/apps/app/src/components/template-catalog-dialog.tsx index 8748e79d5..a87187848 100644 --- a/apps/app/src/components/template-catalog-dialog.tsx +++ b/apps/app/src/components/template-catalog-dialog.tsx @@ -31,8 +31,20 @@ export type TemplateCatalogDialogProps = { onApplied: (result: Applied) => void; }; +const TEMPLATE_CATEGORY_LABEL_KEYS = { + site: "template_market.category.site", + video: "template_market.category.video", + app: "template_market.category.app", + slides: "template_market.category.slides", + poster: "template_market.category.poster", + cards: "template_market.category.cards", + report: "template_market.category.report", + article: "template_market.category.article", + other: "template_market.category.other", +} as const satisfies Record; + function categoryLabel(category: TemplateCategory) { - return t(`template_market.category.${category}`); + return t(TEMPLATE_CATEGORY_LABEL_KEYS[category]); } function TemplateCover(props: { diff --git a/apps/app/src/i18n/locales/en.ts b/apps/app/src/i18n/locales/en.ts index d54951c6f..dde5eb473 100644 --- a/apps/app/src/i18n/locales/en.ts +++ b/apps/app/src/i18n/locales/en.ts @@ -557,7 +557,7 @@ export default { "templates.brief.reference_remove": "Remove {name}", "templates.brief.reference_supported_formats": "Supported: PDF, DOCX, MD, TXT, PNG, JPG, JPEG, WebP, CSV, JSON.", "templates.brief.reference_unsupported_one": "{name} is not a supported reference document.", - "templates.brief.reference_unsupported_many": "{count} files are not supported reference documents.", + "templates.brief.reference_unsupported_other": "{count} files are not supported reference documents.", "templates.brief.reference_prepare_failed": "Could not prepare this reference document.", "templates.brief.reference_autofilled": "Filled title, audience, and details from the reference document. You can edit them before sending.", "templates.brief.submit_failed": "Could not submit this template brief.", diff --git a/apps/app/src/i18n/locales/zh.ts b/apps/app/src/i18n/locales/zh.ts index 625ab2bc3..026a676d1 100644 --- a/apps/app/src/i18n/locales/zh.ts +++ b/apps/app/src/i18n/locales/zh.ts @@ -559,7 +559,7 @@ export default { "templates.brief.reference_remove": "移除 {name}", "templates.brief.reference_supported_formats": "支持:PDF、DOCX、MD、TXT、PNG、JPG、JPEG、WebP、CSV、JSON。", "templates.brief.reference_unsupported_one": "{name} 不是支持的参考文档格式。", - "templates.brief.reference_unsupported_many": "{count} 个文件不是支持的参考文档格式。", + "templates.brief.reference_unsupported_other": "{count} 个文件不是支持的参考文档格式。", "templates.brief.reference_prepare_failed": "无法准备这个参考文档。", "templates.brief.reference_autofilled": "已根据参考文档填入标题、受众和信息,可手动修改。", "templates.brief.submit_failed": "无法提交模板需求,请重试。", diff --git a/apps/app/src/react-app/domains/session/chat/session-page.tsx b/apps/app/src/react-app/domains/session/chat/session-page.tsx index 5a2bdaa4e..3436e105b 100644 --- a/apps/app/src/react-app/domains/session/chat/session-page.tsx +++ b/apps/app/src/react-app/domains/session/chat/session-page.tsx @@ -817,8 +817,8 @@ function TemplateBriefCard({ template, onSubmit, onClose }: { template: Template if (unsupported.length) { toast.warning( unsupported.length === 1 - ? t("templates.brief.reference_unsupported_one", { name: unsupported[0]?.name ?? "" }) - : t("templates.brief.reference_unsupported_many", { count: unsupported.length }), + ? t("templates.brief.reference_unsupported_one", { name: unsupported[0]?.name ?? "" }) + : t("templates.brief.reference_unsupported_other", { count: unsupported.length }), { description: t("templates.brief.reference_supported_formats") }, ); } diff --git a/apps/app/src/react-app/domains/session/templates/template-market-dialog.tsx b/apps/app/src/react-app/domains/session/templates/template-market-dialog.tsx index 1bc62849a..d24797283 100644 --- a/apps/app/src/react-app/domains/session/templates/template-market-dialog.tsx +++ b/apps/app/src/react-app/domains/session/templates/template-market-dialog.tsx @@ -74,7 +74,27 @@ const AUTHORING_TYPES: AuthoringType[] = CATEGORIES.flatMap((category) => catego : [{ ...category, key: category.id }]); const STYLE_ORDER = Object.keys(TEMPLATE_STYLE_LABELS) as TemplateStyle[]; -const templateStyleLabel = (style: TemplateStyle) => t(`template_market.style.${style}`); +const TEMPLATE_STYLE_LABEL_KEYS = { + minimal: "template_market.style.minimal", + editorial: "template_market.style.editorial", + newsprint: "template_market.style.newsprint", + swiss: "template_market.style.swiss", + bold: "template_market.style.bold", + soft: "template_market.style.soft", + pastel: "template_market.style.pastel", + glass: "template_market.style.glass", + dark: "template_market.style.dark", + cyber: "template_market.style.cyber", + technical: "template_market.style.technical", + playful: "template_market.style.playful", + cinematic: "template_market.style.cinematic", + data: "template_market.style.data", + brutalist: "template_market.style.brutalist", + retro: "template_market.style.retro", + sketch: "template_market.style.sketch", + custom: "template_market.style.custom", +} as const satisfies Record; +const templateStyleLabel = (style: TemplateStyle) => t(TEMPLATE_STYLE_LABEL_KEYS[style]); const TEMPLATE_COVER_TIMEOUT_MS = 12_000; const TEMPLATE_COVER_ROOT_MARGIN = "480px 0px"; diff --git a/apps/app/src/react-app/domains/session/video/video-voice-panel.tsx b/apps/app/src/react-app/domains/session/video/video-voice-panel.tsx index 3919f1b23..758a4a016 100644 --- a/apps/app/src/react-app/domains/session/video/video-voice-panel.tsx +++ b/apps/app/src/react-app/domains/session/video/video-voice-panel.tsx @@ -93,12 +93,32 @@ function canSynthesizeCustomVoice(voice: CustomVoice | undefined) { return voice?.status === "OK"; } +const PRESET_VOICE_LABEL_KEYS: Record = { + longanyang: "video.voice.preset_name.longanyang", + longanhuan_v3: "video.voice.preset_name.longanhuan_v3", + longanlang_v3: "video.voice.preset_name.longanlang_v3", + longyingmu_v3: "video.voice.preset_name.longyingmu_v3", +}; + +const PRESET_VOICE_DESCRIPTION_KEYS: Record = { + longanyang: "video.voice.preset_description.longanyang", + longanhuan_v3: "video.voice.preset_description.longanhuan_v3", + longanlang_v3: "video.voice.preset_description.longanlang_v3", + longyingmu_v3: "video.voice.preset_description.longyingmu_v3", +}; + function presetVoiceLabel(voiceId: string) { - return BAILIAN_PRESET_VOICES.some((voice) => voice.id === voiceId) - ? t(`video.voice.preset_name.${voiceId}`) + const labelKey = PRESET_VOICE_LABEL_KEYS[voiceId]; + return BAILIAN_PRESET_VOICES.some((voice) => voice.id === voiceId) && labelKey + ? t(labelKey) : voiceId; } +function presetVoiceDescription(voiceId: string) { + const descriptionKey = PRESET_VOICE_DESCRIPTION_KEYS[voiceId]; + return descriptionKey ? t(descriptionKey) : ""; +} + function customVoiceAvailabilityMessage(voice: CustomVoice | undefined) { if (!voice) return t("video.voice.error.cloned_not_found"); if (voice.status === "DEPLOYING") return t("video.voice.error.cloned_deploying"); @@ -410,7 +430,7 @@ export function VideoVoicePanel({ sessionId, workspaceRoot, client, workspaceId, {activeVoice?.source === "preset" ? : null} diff --git a/apps/app/src/react-app/domains/settings/pages/cloud-marketplaces-view.tsx b/apps/app/src/react-app/domains/settings/pages/cloud-marketplaces-view.tsx index 40f348e7c..4df3baeda 100644 --- a/apps/app/src/react-app/domains/settings/pages/cloud-marketplaces-view.tsx +++ b/apps/app/src/react-app/domains/settings/pages/cloud-marketplaces-view.tsx @@ -54,6 +54,22 @@ const categoryResolutionOrder: Exclude[] = [ "development-operations", ]; +const MARKETPLACE_CATEGORY_LABEL_KEYS = { + "ai-agents": "plugin_library.category.ai-agents", + "development-operations": "plugin_library.category.development-operations", + "design-creative": "plugin_library.category.design-creative", + "productivity-collaboration": "plugin_library.category.productivity-collaboration", + "business-operations": "plugin_library.category.business-operations", + finance: "plugin_library.category.finance", + other: "plugin_library.category.other", +} as const satisfies Record; + +const MARKETPLACE_STATUS_LABEL_KEYS = { + available: "plugin_library.status_available", + installed: "plugin_library.status_installed", + update: "plugin_library.status_update", +} as const satisfies Record, string>; + export type CloudMarketplacesViewProps = { client: iPolloWorkServerClient | null; workspaceId: string | null; @@ -85,8 +101,12 @@ export function resolveMarketplaceCategory(item: { return "other"; } -function categoryLabel(categoryId: MarketplaceCategoryId): string { - return t(`plugin_library.category.${categoryId}`); +export function marketplaceCategoryLabel(categoryId: MarketplaceCategoryId): string { + return t(MARKETPLACE_CATEGORY_LABEL_KEYS[categoryId]); +} + +export function marketplaceStatusLabel(status: Exclude): string { + return t(MARKETPLACE_STATUS_LABEL_KEYS[status]); } function resourcePluginId(resource: EnterpriseResource): string { @@ -314,7 +334,7 @@ export function CloudMarketplacesView({ {!embedded ? categorySections.map((section) => ( {marketplaceCategory === "all" ? t("plugin_library.all_categories") - : t(`plugin_library.category.${marketplaceCategory}`)} + : marketplaceCategoryLabel(marketplaceCategory)} {t("plugin_library.all_categories")} {MARKETPLACE_CATEGORY_IDS.map((categoryId) => ( - {t(`plugin_library.category.${categoryId}`)} + {marketplaceCategoryLabel(categoryId)} ))} @@ -919,7 +921,7 @@ export const PluginPackagesPanel = forwardRef {marketplaceStatus === "all" ? t("plugin_library.all_statuses") - : t(`plugin_library.status_${marketplaceStatus}`)} + : marketplaceStatusLabel(marketplaceStatus)} diff --git a/apps/app/tests/new-conversation-animation-catalog.test.ts b/apps/app/tests/new-conversation-animation-catalog.test.ts index b0e8b7838..924c037b7 100644 --- a/apps/app/tests/new-conversation-animation-catalog.test.ts +++ b/apps/app/tests/new-conversation-animation-catalog.test.ts @@ -60,7 +60,7 @@ describe("new conversation animation catalog", () => { test("edits validated effect variables and sends structured configuration", () => { expect(starter).toContain("function AnimationParameterDialog"); expect(starter).toContain("updateHyperframesEffectVariableOverride"); - expect(starter).toContain("new_conversation.animations.update_${lastUpdate}"); + expect(starter).toContain("ANIMATION_UPDATE_LABEL_KEYS[lastUpdate]"); expect(surface).toContain("hyperframesSelectionPayload(selection)"); expect(surface).toContain("data-variable-values/getVariables"); });