open-design/apps/landing-page/app/content-i18n.ts
Jane f8c860a505
feat(landing-page): localize plugins library across 18 locales (#3010)
* feat(landing-page): localize plugins library across 18 locales

PR #2926 shipped the new `/plugins/` library hub + four kind sub-routes
+ detail pages, but the chrome was English-only — visitors landing on
`/zh/plugins/` saw the old marketplace registry placeholder rendered
by the catch-all instead, and detail pages rendered identical English
copy regardless of locale prefix. This PR brings the plugin surface
to feature parity with `/zh/skills/`, `/zh/templates/`, `/zh/systems/`,
`/zh/craft/`.

## What changes

- New `app/_lib/plugins-i18n.ts` — single source for all plugin chrome
  copy (hub, list pages, chip rails, share dialog, detail-page meta
  labels). English baseline + 17 locale overrides keyed on
  `LandingLocaleCode` (the same short-code shape `localeFromPath()`
  returns). Missing keys per locale fall back to English so a
  partially-translated locale still renders sensibly. Translations
  cover hub copy, four tile titles + blurbs, seven artifact-kind
  labels + descriptions, 23 scene-subcategory labels, 18 detail-page
  chrome strings, and a six-key share-dialog table with a
  per-locale `shareTemplate({title, url})` function (translated for
  every locale where `_lib/i18n.ts` already had one — same voice).
- `app/pages/plugins/{,templates/,templates/[kind]/,skills/,systems/,
  craft/,[slug]/}/index.astro` — every hardcoded English string now
  reads `getPluginsCopy(locale)` keys. Page logic and routing
  unchanged.
- New short-code wrappers under `app/pages/[locale]/plugins/` — six
  files (hub + three sub-routes + `[kind]/` and `[slug]/`) following
  the same pattern `[locale]/skills/index.astro` already uses: each
  re-exports the canonical page component and adds a per-locale
  `getStaticPaths()` so the build emits 17 locale prefixes per
  plugin route. Total plugin-route prerender count goes from ~390 to
  ~7 000, matching the existing skill/template scaling.
- Catch-all (`[locale]/[...path].astro`) — old `getPublicPlugins` /
  `getRegistryCounts` registry rendering removed (placeholder UI
  that was never wired to a real marketplace data source). Plugin
  routes now live exclusively under `[locale]/plugins/...` short-code
  wrappers, so the catch-all stops claiming `'plugins'` as a route
  root. The dead-code path also drops a `pluginCounts.all` reference
  the title row was reading.
- `.plugins-tile-grid` styles promoted from a scoped `<style>` in the
  default-locale hub to global `app/sub-pages.css` so the
  short-code wrapper renders the same hub markup without re-mounting
  per-page CSS — `display: contents`-style scoping pitfalls in
  Astro's per-component CSS scoping made this the cleanest fix.

## Surface area

- [ ] **UI** — new page / dialog / panel / menu item / setting / empty state in `apps/web` or `apps/desktop`
- [ ] **Keyboard shortcut** — new or changed
- [ ] **CLI / env var** — new `od` subcommand or flag, new `tools-dev` flag, or new `OD_*` env var
- [ ] **API / contract** — new `/api/*` endpoint, new SSE event, or changed shape in `packages/contracts`
- [ ] **Extension point** — new entry under `skills/`, `design-systems/`, `design-templates/`, or `craft/`, or change to the skills protocol
- [ ] **i18n keys** — new translation keys (full plugin chrome added across all 18 locales)
- [ ] **New top-level dependency** — adding any new entry to the **root** `package.json`
- [ ] **Default behavior change** — changes what existing users experience without opting in
- [x] **None** — landing-page-only restoration of i18n parity for the plugin surface

## Validation

- `pnpm --filter @open-design/landing-page typecheck` → 0 errors
- `pnpm --filter @open-design/landing-page build:static` → 16 127 pages
  built (+6 584 over current main: ~388 plugin detail pages × 17
  locale prefixes plus the hub + four sub-routes × 17 locales).
- `copy-example-html.ts` reports `266 entry files + 65 referenced
  files`, identical to before — no regression in the asset-mirroring
  pipeline.
- Local Playwright smoke (`/zh/plugins/...`):
  - `/zh/plugins/` renders `<title>插件库 · Open Design</title>`,
    label `插件库`, h1 `407 个可组合的构件。`, four tiles labelled
    `模板 / 技能 / 设计系统 / 工艺`.
  - `/zh/plugins/templates/video/` renders h1 `48 视频`, scene chips
    `全部 / 动效 / 短视频 / 营销 / 产品 / 数据讲解`.
  - `/zh/plugins/example-article-magazine/` share dialog renders
    `复制下面的文案、然后跳到你想分享的平台粘贴即可` etc., share
    template auto-interpolates plugin title + URL into Chinese voice.
  - All 18 locale prefixes (`/zh`, `/zh-tw`, `/ja`, `/ko`, `/de`,
    `/fr`, `/ru`, `/es`, `/pt-br`, `/it`, `/vi`, `/pl`, `/id`, `/nl`,
    `/ar`, `/tr`, `/uk`) → 200 across hub + four sub-routes + sample
    detail page.
  - English `/plugins/` unchanged (default-locale path bypasses the
    `[locale]/...` wrapper).

* feat(landing-page): finish plugins i18n chrome across 18 locales

The first localization pass shipped a partial fix: hub headings, lead
copy, two-level page chrome, detail-page metadata labels, the share
dialog, and the chip rail were still falling back to English on every
non-English locale because plugins-i18n.ts only filled a chrome slice
for `zh` and the file header even claimed "7 artifact-kind labels and
25 scene-subcategory labels are translated" for every locale that did
not yet have those blocks.

Three changes close the visible gap:

1. plugins-i18n.ts: fills the 27 still-missing chrome fields per locale
   for zh-tw / ja / ko / de / fr / ru / es / pt-br / it / vi / pl / id /
   nl / ar / tr / uk. Includes the 7-key category map, the 23-key
   subcategory map, hubHeading / hubLead, the 4 *Label / *Heading /
   *Lead triples for the templates / skills / systems / craft hub
   pages, the 4 tile blurbs, the 4 browse buttons, sceneLabel, allChip,
   the 12 detail-page metadata labels (mode / scenario / platform /
   surface / author / manifest id / tags / preview caption / find on
   GitHub / homepage / open in new tab) and bucket label map, the
   detail share dialog (title / copy link / jump-to), and the
   header-side nav.plugins entry. zh receives the same 11 detail-page
   and share-dialog labels it was also missing.

2. header.tsx + site-footer.astro: routes the hardcoded "Plugins /
   Templates / Skills / Systems / Craft" labels through `nav.*` from
   HeaderCopy, so every locale gets its own dropdown trigger and
   footer column. Adds `nav.plugins` to HeaderCopy and fills it in 18
   locales with the local form ("插件" / "プラグイン" / "Plugins" /
   "Plug-ins" / "Plaginy" / "الإضافات" / etc).

3. plugin-row.astro + content-i18n.ts: chip rail. The bundled-plugin
   branch now runs raw `mode` / `scenario` slugs through the shared
   localizeTaxonomyValue, and that helper now also consults the
   plugins-i18n subcategory map before giving up. localizeTaxonomyValue
   now returns undefined on a true miss instead of the unknownTag
   placeholder, so chips drop quietly instead of showing "Category" /
   "分類" / "Categoría" for taxonomy slugs we have not localized yet.
   Callers that genuinely want the placeholder (`localizeContentTag`,
   blog `category`, system noun) still keep the explicit fallback.

Out of scope and tracked separately: per-plugin title and description
in plugins/_official/* (author-supplied English metadata, ~401 plugins
without an i18n schema in the manifest yet — needs RFC + tooling
before the manifests can be expanded), and adding the long tail of
mode / scenario / category slugs (`code-migration`, `plugin-sharing`,
`tune-collab`, `live-artifacts`, `engineering`, ...) to TAXONOMY_TERMS
so chips render localized labels for every taxonomy value rather than
dropping silently.

* feat(landing-page): cover plugins chip rail long-tail taxonomy slugs

PR #3010's first round localized the high-frequency mode/scenario
chips (prototype, video, image, marketing, design, ...) but left the
~37 mode/scenario and 14 category slugs that show up in real `od.*`
metadata — code-migration, plugin-sharing, design-system, planning,
scenario, refine, discovery, handoff, token-map, tune-collab, orbit,
live-artifacts, engineering, healthcare, hr, sales, support,
default-router, downstream-export, figma-migration, media-generation,
plugin-authoring, validation, 3d-shaders, animation-motion,
audio-music, creative-direction, design-systems, diagrams, documents,
image-generation, marketing-creative, screenshots, slides,
video-generation, web-artifacts, ... — falling through to undefined
and dropping their chip silently on every non-English locale.

The data layer is the source of truth here, so this expansion lands
in `content-i18n.ts:TAXONOMY_TERMS` / `CATEGORY_LABELS` rather than
the plugins-i18n catalog: a single dictionary entry per slug fans out
to every chip-rail consumer (catalog rows, detail metadata, the
templates/[kind] facets) without each consumer touching its own copy.

Translations cover all 17 non-`en` locales. Brand and product nouns
(Figma, Open Design, BYOK, plugin) stay literal; technical taxonomy
slugs get short equivalents that read as chips rather than full
prose. The result on `/ja/plugins/skills/` matches `/plugins/skills/`
chip-for-chip (30 chips both sides) instead of dropping 27 of them
the way the previous iteration did.

* feat(landing-page): read manifest title_i18n / description_i18n on bundled plugins

PR #3010's prior rounds localized chrome and chip rails but the
catalog's most prominent text — each row's plugin name and blurb —
stayed English on every non-English locale. The plugin manifest
schema (`packages/contracts/src/plugins/manifest.ts`) has supported
`title_i18n` and `description_i18n` (Record<locale, string>) on every
manifest from spec v1; ~24 of the 401 first-party manifests already
carry one for `zh-CN`. The reader was just never wired to use them.

This change does the reader half: bundled-plugins.ts captures the
two i18n maps off each `open-design.json`, plugin-row.astro and the
detail page resolve them at render time via two new helpers
(`resolveBundledTitle`, `resolveBundledDescription`) that mirror the
short→long fallback chain documented in the manifest spec
(`htmlLang` like `zh-CN` → short `LandingLocaleCode` like `zh` →
primary tag → `en` → English baseline). The static-paths pass still
runs once for all locales — it has to, since each manifest produces
one URL — but the title/description shown on the rendered page now
reads the locale off `Astro.url.pathname` and picks the right entry
out of the maps.

Verified locally: `/zh/plugins/example-card-twitter/` now reads
"Twitter 分享卡 / 推特金句 / 数据卡, 适合配推文" from the manifest's
existing `zh-CN` block instead of the English baseline.

Plugin-data half follows in a separate commit. The 17 non-English
locales × 401 manifests need backfilling so the reader has something
to resolve to; that's data, not schema, and lands as a sequence of
manifest patches rather than tangled with this code change.

* feat(plugins): translate scenarios bucket title/description across 17 locales

Closes the first chunk of #3028. Eleven scenarios plugins (the
default-scenario bundle for each taskKind: code-migration,
figma-migration, media-generation, new-generation, tune-collab,
plugin-authoring; the default design router; the React / Vue /
Next.js downstream-export starters; and the Refine baseline) get
title_i18n + description_i18n filled for all 17 non-English locales
the landing page serves (zh-CN, zh-TW, ja, ko, de, fr, ru, es,
pt-BR, it, vi, pl, id, nl, ar, tr, uk).

The reader landed in 7ddfe36; this commit is data-only. taskKind
slugs that other docs reference by name (`code-migration`,
`figma-migration`, `tune-collab`, etc.) stay literal in the
descriptions so cross-references still resolve. Brand nouns —
Open Design, Next.js, React, Vue, Figma — also stay literal.

`/ja/plugins/od-code-migration/` now reads
"コードマイグレーション(デフォルトシナリオ)" instead of the English
baseline; `/zh/plugins/skills/` shows "代码迁移(默认场景)" in the
catalog row.

Remaining buckets (image-templates 45, video-templates 50,
examples 140, design-systems 142 = 377 plugins) follow in
subsequent commits in this PR.

* fix(landing-page): drop CJK template wrap when source name is still English

The Chinese / Japanese / Korean fallback templates for craft, skill,
template, system, plugin, and blog text splice the source `name` /
`title` into a CJK sentence frame: ``${name}工艺规则``,
``Open Design 指南:${topic}``, ``${name} は…のスキルです``. When the
underlying SKILL.md / craft markdown / blog frontmatter still ships
an English name (true for ~95% of the catalog today), that produces
mid-sentence script straddling on `/zh/...`, `/zh-tw/...`, `/ja/...`,
`/ko/...` like:

  H1   : "Editorial typography hierarchy工艺规则"
  Lead : "这条 Open Design 工艺规则定义 Editorial typography hierarchy
          的执行标准…"
  Plug : "video 插件 · 3D Animated Boy Building Lego"

That reads worse than the all-English fallback, because the visitor
parses the page in two scripts at once.

Adds a `nameNeedsEnglishFallback` guard that fires for the four CJK
locales whenever the spliced-in name has no CJK characters of its
own, and threads it through every `localizeXxxText` helper:
craft, template, system, plugin, skill, blog. When it fires the
helper returns the raw English content untouched, so the section
renders end-to-end in one language. Chrome (header, footer, breadcrumb,
buttons, share dialog) keeps its CJK rendering — only the
title-and-lead block falls back.

Side benefit: the same guard kicks in on the long tail of plugin
manifests still pending `title_i18n` / `description_i18n` backfill
(tracked in #3028), so `/zh/plugins/<bundled>/` no longer pairs a
"video 插件 · 3D Animated Boy Building Lego" title with a Chinese
breadcrumb. The page reads "3D Animated Boy Building Lego" + the
English manifest description, while header / footer / breadcrumbs
stay localized. Once a manifest ships its i18n maps, the chrome and
body re-converge automatically.

Non-CJK non-Latin scripts (ar, vi, ...) keep the previous behavior —
their templates already read tolerably with English names. If that
turns out to be wrong on a real audit, the same guard generalizes by
adding the matching Unicode range and locale set.

* feat(plugins): translate image-templates bucket title/description across 17 locales

44 of 45 image-templates plugins get title_i18n + description_i18n
filled for all 17 non-English locales (zh-CN, zh-TW, ja, ko, de, fr,
ru, es, pt-BR, it, vi, pl, id, nl, ar, tr, uk). Generated via Claude
Sonnet 4.5 over the OpenRouter gateway, ~$1.38 in API spend, 156s
wall-clock. Brand and cultural references stay literal (Open Design,
Lego, Hanfu, Showa, Pokémon, Black Myth: Wukong). Long AI generation
prompts collapse to a 1-2 sentence summary capturing what the plugin
does — the description doubles as catalog blurb on the landing site,
not as the actual generation prompt (which lives in example.html /
the manifest's preview entry).

Skipped: `profile-avatar-realistically-imperfect-ai-selfie` returned
malformed JSON on three retries; will rerun with a tighter prompt in
a follow-up commit. Catalog rows for that plugin keep falling back to
the raw English fields per #3010's reader change, so nothing breaks.

Tracking: closes the image-templates row in #3028.

* feat(plugins): translate video-templates bucket title/description across 17 locales

49 of 50 video-templates plugins get title_i18n + description_i18n
filled for the 17 non-English landing locales. Generated via Claude
Sonnet 4.5 over OpenRouter, ~$1.47 in API spend, 177s wall-clock.
HyperFrames templates, the Three Kingdoms cinematic series, the
Seedance/short-film prompts, and the K-pop / wuxia / anime variants
all get a 1-2 sentence catalog blurb in each locale; brand and
cultural tokens (Black Myth: Wukong, Hanfu, Showa, Pokémon, Three
Kingdoms / 三国志, Lego, Disney, K-pop, HyperFrames) stay literal.

Skipped: `live-action-anime-adaptation-water-vs-thunder-breathing-duel`
returned malformed JSON on three retries; will rerun in followup.
Falls back to the raw English fields per the reader landed in 7ddfe36.

Tracking: closes the video-templates row in #3028.

* feat(plugins): translate examples bucket (117/140) title/description across 17 locales

117 of 140 examples plugins get title_i18n + description_i18n filled
for the 17 non-English landing locales. Generated via Claude Sonnet
4.5 over OpenRouter, $3.94 in API spend, ~13 min wall-clock at
8-way concurrency. Existing zh-CN translations on 24 manifests are
preserved (the merge keeps author-supplied entries and only adds
missing locales).

23 of 140 returned malformed JSON on three retries — the output
likely hit the 4000 max_tokens ceiling on plugins whose description
balloons across 17 locales. Those manifests fall back to English on
non-`en` rendering per the reader landed in 7ddfe36, and will rerun
in a follow-up commit with a larger token budget and a stricter
output schema.

Tracking: closes 117/140 of the examples row in #3028; the remaining
23 stay open in that issue's failure list.

* feat(plugins): translate design-systems bucket (141/142) title/description across 17 locales

141 of 142 design-systems plugins get title_i18n + description_i18n
filled for the 17 non-English landing locales. Generated via Claude
Sonnet 4.5 over OpenRouter, $2.55 in API spend, 301s wall-clock at
8-way concurrency.

Translator script gained two improvements between examples and this
bucket:
- max_tokens bumped from 4000 to 8000 so 17-locale outputs stop
  truncating on the long-tail manifests with verbose descriptions
- a balanced-brace JSON extractor that pulls the outermost `{ ... }`
  from the response, tolerating trailing prose Claude occasionally
  appends after the JSON object.

Result: only 1 manifest (`totality-festival`) failed parse this
batch, down from ~16% on the examples bucket. The next commit
re-runs the prior buckets' failures with the improved script.

Tracking: closes 141/142 of the design-systems row in #3028.

* fix(plugins): backfill 4 plugins that retried green after JSON extractor improvement

dcf-valuation, social-media-dashboard, wireframe-sketch (examples
bucket) and live-action-anime-adaptation-water-vs-thunder-breathing-duel
(video-templates bucket) parse cleanly under the balanced-brace
extractor introduced for the design-systems batch. The remaining
22 failures from the prior runs hit a different parse mode (Claude
emitting unescaped double quotes inside string values when the source
description contains its own English quotes like 'make it professional');
those will need a tighter prompt and rerun.

* fix(plugins): translate the last 22 plugins with quote-handling prompt fix

The 22 stuck plugins all carried English / Chinese double-quoted
phrases inside their description (\"make it professional\",
\"What's inside\", \"电子杂志 × 电子墨水\") that Claude was emitting
back inside JSON string values without escaping, breaking the parse.

Added one rule to the translator prompt — never use a straight double
quote inside a translated string, prefer single quotes / curly quotes
/ CJK 『 』 / 《 》 — and the previously stuck batch sailed through
clean: 22/22 ok, 0 retries, $0.85.

This closes the long tail of #3028:
- scenarios   11/11   ✓
- image-templates 45/45 ✓
- video-templates 50/50 ✓
- examples    140/140  ✓
- design-systems 142/142 ✓
- atoms       N/A (filtered from public catalog)

All 388 catalog-visible plugins now ship title_i18n + description_i18n
for all 17 non-English locales the landing page serves.

* fix(plugins): clean up four review-flagged i18n data issues

- apps/landing-page/app/_lib/plugins-i18n.ts:759 — Polish bucket
  label `examples: 'Przyklad'` was missing the diacritic; every
  other Polish string in the same block uses proper diacritics.
  Restore to 'Przykład'. (Reviewer: looper #4364985878.)

- video-templates/cinematic-route-navigation-guide — German
  title_i18n.de was a byte-for-byte copy of en ("Cinematic Route
  Navigation Guide") while the German description was already
  translated. Replace with "Cinematischer Routen-Navigationsleitfaden"
  to match the German voice the description sets.

- video-templates/hollywood-haute-couture-fantasy-video-prompt —
  Dutch title_i18n.nl was identical to en for the same reason.
  Translate the trailing noun phrase: "Hollywood Haute Couture
  Fantasy Videoprompt" (mirrors the Dutch description's compound
  word style).

- video-templates/video-seedance-three-kingdoms-guanyu-slaying-yanliang —
  Korean Hangul `돌진` had leaked into the Turkish description (a
  translation-pipeline artifact where the model copied the verb
  from the Korean output without translating it). Replace
  "saflarına돌진 eder" with the idiomatic Turkish "saflarına dalar".

All four are data-only fixes against existing manifests; no schema
changes, no reader changes. typecheck stays at 0 errors.

* fix(landing-page): localize aria-labels, alt text and BreadcrumbList JSON-LD on plugin detail page

The PR's prior rounds left six accessibility / structured-data
surfaces on `/{locale}/plugins/<slug>/` either entirely English or
mixing English chrome with the localized plugin title. Reviewer
flagged each one across multiple loops; this commit clears them all:

1. `aria-label` on the open-in-new-tab popout no longer reuses the
   visible label `pcopy.detailOpenInNewTab` (which carries the
   decorative `↗`). Added `detailOpenInNewTabAria` — same wording,
   no glyph — and the `<a aria-label>` consumes that key. The
   visible link text still ends in `↗`.

2. `<nav class="breadcrumb" aria-label="Breadcrumb">` now reads
   `aria-label={pcopy.breadcrumbLabel}`. Eighteen locales filled
   ("面包屑导航", "パンくずリスト", "Brotkrumen-Navigation",
   "Fil d'Ariane", "مسار التنقل", "İçerik haritası", ...).

3. Share-dialog `<button aria-label="Close">` now reads
   `aria-label={pcopy.shareDialogClose}`. Eighteen locales filled
   ("关闭", "閉じる", "Cerrar", "Закрыть", "إغلاق", ...).

4. Three template-literal a11y strings (`${pluginTitle} preview`,
   `Open interactive preview for ${pluginTitle}`, `${pluginTitle}
   interactive preview`) become function calls
   (`pcopy.previewImageAlt(t)`, `previewSummaryAria(t)`,
   `previewIframeTitle(t)`) so the sentence frame around the
   plugin title rotates with the page locale. Two `<img alt>` call
   sites (the static preview at line 210 and the click-to-expand
   thumbnail at line 179) both consume `previewImageAlt`.

5. `BreadcrumbList` JSON-LD position-2 now reads
   `name: pcopy.hubLabel` instead of hardcoded English `"Plugins"`.
   The visible breadcrumb at line 105 already renders
   `pcopy.hubLabel`; this aligns the structured data with the
   rendered chrome on every locale.

The new function-typed keys deliberately interpolate `pluginTitle`
(which is itself locale-resolved via `resolveBundledTitle`) so the
mixed-language guard from commit 002d457 is preserved: a manifest
without a per-locale title still flows through to a coherent
single-language a11y string because `pluginTitle` falls back to
English along with the rest of the section.

apps/landing-page typecheck stays at 0 errors.

Closes reviewer threads:
- #pullrequestreview-4364985878 (Open in new tab aria)
- #pullrequestreview-4368926224 (Polish typo + plus mixed-language alt/aria)
- #4373... (BreadcrumbList JSON-LD)
- #4374... (aria-label="Close" + aria-label="Breadcrumb")

* fix(landing-page): redirect legacy fa/hu/th /plugins/ paths to canonical

When the new `/{locale}/plugins/...` short-code wrappers landed, the
legacy catch-all `pages/[locale]/[...path].astro` dropped `'plugins'`
from its `paths` list. That intentionally avoids serving stale
marketplace-registry placeholder routes for the modern landing
locales — but it also takes `/fa/plugins/`, `/hu/plugins/`, and
`/th/plugins/` from 200 to 404, because those three legacy locales
live only in the old `_lib/i18n.ts:LOCALES` set and are not part of
`LANDING_LOCALES` (the modern 18-locale list the new wrappers serve).

Three `301`s in `_redirects` send those legacy URLs to the canonical
English `/plugins/...` so SEO and inbound links keep working until
the legacy locale set is retired entirely.

Reviewer thread (#pullrequestreview-4364052045) flagged this as a
non-blocking regression across multiple loops; this commit closes it.

* ci(landing-page): add merge_group trigger so the queue can clear PRs

`landing-page-ci.yml` only fired on `pull_request` and `push:main`,
which meant the required `Validate landing page` and
`Strict PR visual tests` checks never dispatched against the
`merge_group` ref the merge queue creates. The queue then sat at
"awaiting checks" until it timed out and ejected the PR (the
deadlock observed during the 5/26 release window).

Adding a `merge_group: { types: [checks_requested] }` trigger to
the same workflow lets the queued ref reuse the existing job graph,
matching the pattern in `ci.yml` which already wires `merge_group`.

Also drops `plugins/**` into the same paths filter as `pull_request`
since the new bundled-plugins reader (commit 7ddfe364) consumes
those manifests' `title_i18n` / `description_i18n` maps and the
landing-page CI must rerun when manifest data changes.

---------

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-27 09:30:59 +00:00

899 lines
82 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
DEFAULT_LOCALE,
getLocaleDefinition,
type LandingLocaleCode,
type LocalizedStringValue,
} from './i18n';
import { getPluginsCopy } from './_lib/plugins-i18n';
type ContentCopy = {
skillNoun: string;
systemNoun: string;
templateNoun: string;
craftNoun: string;
pluginNoun: string;
blogNoun: string;
unknownTag: string;
skillDescription: (name: string, labels: string[]) => string;
systemTagline: (name: string, category: string) => string;
systemAtmosphere: (name: string, category: string, paletteCount: number) => string;
craftName: (name: string) => string;
craftSummary: (name: string) => string;
templateName: (name: string) => string;
templateSummary: (name: string) => string;
pluginTitle: (kind: string, id: string) => string;
pluginDescription: (kind: string, labels: string[]) => string;
pluginExample: (kind: string) => string;
blogTitle: (topic: string) => string;
blogSummary: (topic: string) => string;
blogBody: (topic: string, summary: string) => string;
};
const CONTENT_COPY: Record<Exclude<LandingLocaleCode, 'en'>, ContentCopy> = {
zh: {
skillNoun: 'Skill',
systemNoun: '设计系统',
templateNoun: '模板',
craftNoun: '工艺规则',
pluginNoun: '插件',
blogNoun: '文章',
unknownTag: '分类',
skillDescription: (name, labels) => `${name} 是一个可组合的 Open Design Skill用于${labels.join('、') || '设计产出'}工作流;可由本地代理调用,并和仓库中的设计系统一起复用。`,
systemTagline: (name, category) => `${name} 设计系统将${category}风格整理成可移植的 DESIGN.md 规则,供每个 Skill 复用。`,
systemAtmosphere: (name, category, paletteCount) => `${name}${category}为视觉方向,包含 ${paletteCount} 个核心色板、排版节奏、组件边界和反模式约束。`,
craftName: (name) => `${name}工艺规则`,
craftSummary: (name) => `这条 Open Design 工艺规则定义 ${name} 的执行标准,帮助代理在生成 artifact 时保持一致、可读和可交付。`,
templateName: (name) => `${name}模板`,
templateSummary: (name) => `${name} 是可复用的 Open Design Live Artifact 模板,包含渲染入口、示例数据和可 fork 的文件结构。`,
pluginTitle: (kind, id) => `${kind}插件 · ${id}`,
pluginDescription: (kind, labels) => `用于${kind}工作流的 Open Design 插件。安装后可在本地 daemon 和 od CLI 中复用${labels.length ? `,覆盖${labels.join('、')}` : ''}`,
pluginExample: (kind) => `使用该插件创建一个${kind}任务,并在本地 Open Design 工作区中查看生成结果。`,
blogTitle: (topic) => `Open Design 指南:${topic}`,
blogSummary: (topic) => `这篇本地化摘要说明 ${topic} 与 Open Design 的本地优先、BYOK 和可组合 Skill 工作流之间的关系。`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>本地化摘要</h2><p>这篇文章围绕 ${topic} 展开,说明 Open Design 如何把设计 artifact、Skill、设计系统和本地代理工作流连接起来。</p><p>当前页面使用站内 i18n fallback 渲染本地化正文;完整人工翻译可继续通过 frontmatter 的 <code>i18n.bodyHtml</code> 覆盖。</p>`,
},
'zh-tw': {
skillNoun: 'Skill',
systemNoun: '設計系統',
templateNoun: '模板',
craftNoun: '工藝規則',
pluginNoun: '外掛',
blogNoun: '文章',
unknownTag: '分類',
skillDescription: (name, labels) => `${name} 是一個可組合的 Open Design Skill用於${labels.join('、') || '設計產出'}工作流;可由本地代理呼叫,並和 repo 中的設計系統一起複用。`,
systemTagline: (name, category) => `${name} 設計系統將${category}風格整理成可攜式 DESIGN.md 規則,供每個 Skill 複用。`,
systemAtmosphere: (name, category, paletteCount) => `${name}${category}為視覺方向,包含 ${paletteCount} 個核心色板、排版節奏、元件邊界和反模式約束。`,
craftName: (name) => `${name}工藝規則`,
craftSummary: (name) => `這條 Open Design 工藝規則定義 ${name} 的執行標準,幫助代理在生成 artifact 時保持一致、可讀和可交付。`,
templateName: (name) => `${name}模板`,
templateSummary: (name) => `${name} 是可複用的 Open Design Live Artifact 模板,包含渲染入口、示例資料和可 fork 的檔案結構。`,
pluginTitle: (kind, id) => `${kind}外掛 · ${id}`,
pluginDescription: (kind, labels) => `用於${kind}工作流的 Open Design 外掛。安裝後可在本地 daemon 和 od CLI 中複用${labels.length ? `,覆蓋${labels.join('、')}` : ''}`,
pluginExample: (kind) => `使用該外掛建立一個${kind}任務,並在本地 Open Design 工作區中查看生成結果。`,
blogTitle: (topic) => `Open Design 指南:${topic}`,
blogSummary: (topic) => `這篇本地化摘要說明 ${topic} 與 Open Design 的本地優先、BYOK 和可組合 Skill 工作流之間的關係。`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>本地化摘要</h2><p>這篇文章圍繞 ${topic} 展開,說明 Open Design 如何把設計 artifact、Skill、設計系統和本地代理工作流連接起來。</p><p>目前頁面使用站內 i18n fallback 渲染本地化正文;完整人工翻譯可繼續透過 frontmatter 的 <code>i18n.bodyHtml</code> 覆蓋。</p>`,
},
ja: {
skillNoun: 'スキル',
systemNoun: 'デザインシステム',
templateNoun: 'テンプレート',
craftNoun: 'クラフトルール',
pluginNoun: 'プラグイン',
blogNoun: '記事',
unknownTag: '分類',
skillDescription: (name, labels) => `${name} は、${labels.join('、') || 'デザイン制作'}のための Open Design スキルです。ローカルエージェントから呼び出せ、リポジトリ内のデザインシステムと一緒に再利用できます。`,
systemTagline: (name, category) => `${name}${category} の方向性を DESIGN.md として整理した、移植可能なデザインシステムです。`,
systemAtmosphere: (name, category, paletteCount) => `${name}${category} を基調に、${paletteCount} 個のパレット、タイポグラフィ、コンポーネント境界、避けるべきパターンを定義します。`,
craftName: (name) => `${name} のクラフトルール`,
craftSummary: (name) => `${name} の実行基準を定義し、エージェントが一貫して読みやすく納品可能な artifact を生成できるようにします。`,
templateName: (name) => `${name} テンプレート`,
templateSummary: (name) => `${name} は再利用可能な Open Design Live Artifact テンプレートで、レンダー入口、サンプルデータ、fork 可能な構成を含みます。`,
pluginTitle: (kind, id) => `${kind} プラグイン · ${id}`,
pluginDescription: (kind, labels) => `${kind} ワークフロー向けの Open Design プラグインです。インストール後はローカル daemon と od CLI から再利用できます${labels.length ? `。対象: ${labels.join('、')}` : ''}`,
pluginExample: (kind) => `このプラグインで ${kind} タスクを作成し、ローカルの Open Design ワークスペースで結果を確認します。`,
blogTitle: (topic) => `Open Design ガイド: ${topic}`,
blogSummary: (topic) => `${topic} と、Open Design のローカルファースト、BYOK、構成可能なスキルワークフローの関係をまとめます。`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>ローカライズ概要</h2><p>この記事は ${topic} を起点に、Open Design が artifact、スキル、デザインシステム、ローカルエージェントをどう接続するかを説明します。</p><p>このページは i18n fallback で本文を表示しています。完全な人手翻訳は frontmatter の <code>i18n.bodyHtml</code> で上書きできます。</p>`,
},
ko: {
skillNoun: '스킬',
systemNoun: '디자인 시스템',
templateNoun: '템플릿',
craftNoun: '크래프트 규칙',
pluginNoun: '플러그인',
blogNoun: '글',
unknownTag: '분류',
skillDescription: (name, labels) => `${name}${labels.join(', ') || '디자인 산출물'} 워크플로를 위한 조합 가능한 Open Design 스킬입니다. 로컬 에이전트가 호출하고 저장소의 디자인 시스템과 함께 재사용할 수 있습니다.`,
systemTagline: (name, category) => `${name} 디자인 시스템은 ${category} 방향을 이식 가능한 DESIGN.md 규칙으로 정리합니다.`,
systemAtmosphere: (name, category, paletteCount) => `${name}${category} 분위기를 바탕으로 ${paletteCount}개의 팔레트, 타이포그래피 리듬, 컴포넌트 경계, 안티패턴을 정의합니다.`,
craftName: (name) => `${name} 크래프트 규칙`,
craftSummary: (name) => `${name}의 실행 기준을 정의해 에이전트가 일관되고 읽기 쉬운 artifact를 만들도록 돕습니다.`,
templateName: (name) => `${name} 템플릿`,
templateSummary: (name) => `${name}은 재사용 가능한 Open Design Live Artifact 템플릿이며 렌더링入口, 샘플 데이터, fork 가능한 구조를 포함합니다.`,
pluginTitle: (kind, id) => `${kind} 플러그인 · ${id}`,
pluginDescription: (kind, labels) => `${kind} 워크플로용 Open Design 플러그인입니다. 설치 후 로컬 daemon과 od CLI에서 재사용할 수 있습니다${labels.length ? `: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `이 플러그인으로 ${kind} 작업을 만들고 로컬 Open Design 워크스페이스에서 결과를 확인합니다.`,
blogTitle: (topic) => `Open Design 가이드: ${topic}`,
blogSummary: (topic) => `${topic}이 Open Design의 로컬 우선, BYOK, 조합 가능한 스킬 워크플로와 어떻게 연결되는지 요약합니다.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>현지화 요약</h2><p>이 글은 ${topic}을 중심으로 Open Design이 artifact, 스킬, 디자인 시스템, 로컬 에이전트를 어떻게 연결하는지 설명합니다.</p><p>현재 본문은 i18n fallback으로 렌더링됩니다. 완전한 번역은 frontmatter의 <code>i18n.bodyHtml</code>로 덮어쓸 수 있습니다.</p>`,
},
de: {
skillNoun: 'Skill',
systemNoun: 'Designsystem',
templateNoun: 'Vorlage',
craftNoun: 'Gestaltungsregel',
pluginNoun: 'Plugin',
blogNoun: 'Artikel',
unknownTag: 'Kategorie',
skillDescription: (name, labels) => `${name} ist ein kombinierbarer Open-Design-Skill fuer ${labels.join(', ') || 'Design-Artefakte'}. Er laesst sich lokal vom Agenten ausfuehren und mit DESIGN.md-Systemen wiederverwenden.`,
systemTagline: (name, category) => `${name} buendelt die Richtung ${category} als portables DESIGN.md-System fuer alle Skills.`,
systemAtmosphere: (name, category, paletteCount) => `${name} uebersetzt ${category} in ${paletteCount} Kernfarben, Typografie, Komponentenregeln und Anti-Patterns.`,
craftName: (name) => `${name}-Gestaltungsregel`,
craftSummary: (name) => `Diese Open-Design-Regel definiert Standards fuer ${name}, damit Agenten konsistente und lieferbare Artefakte erzeugen.`,
templateName: (name) => `${name}-Vorlage`,
templateSummary: (name) => `${name} ist eine wiederverwendbare Live-Artifact-Vorlage mit Render-Einstieg, Beispieldaten und forkbarer Struktur.`,
pluginTitle: (kind, id) => `${kind}-Plugin · ${id}`,
pluginDescription: (kind, labels) => `Open-Design-Plugin fuer ${kind}-Workflows. Nach der Installation ist es lokal ueber daemon und od CLI nutzbar${labels.length ? `; Schwerpunkte: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Erstelle mit diesem Plugin eine ${kind}-Aufgabe und pruefe das Ergebnis im lokalen Open-Design-Workspace.`,
blogTitle: (topic) => `Open-Design-Leitfaden: ${topic}`,
blogSummary: (topic) => `Lokalisierte Zusammenfassung zu ${topic} und dem lokalen BYOK-Skill-Workflow von Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Lokalisierte Zusammenfassung</h2><p>Dieser Beitrag erklaert, wie Open Design ${topic} mit Artefakten, Skills, Designsystemen und lokalen Agenten verbindet.</p><p>Der Text nutzt aktuell einen i18n-Fallback. Eine vollstaendige Uebersetzung kann ueber <code>i18n.bodyHtml</code> im Frontmatter hinterlegt werden.</p>`,
},
fr: {
skillNoun: 'skill',
systemNoun: 'systeme de design',
templateNoun: 'modele',
craftNoun: 'regle de conception',
pluginNoun: 'plugin',
blogNoun: 'article',
unknownTag: 'categorie',
skillDescription: (name, labels) => `${name} est un skill Open Design composable pour les flux ${labels.join(', ') || 'de production design'}. Il s'execute avec l'agent local et se reutilise avec les systemes DESIGN.md.`,
systemTagline: (name, category) => `${name} transforme la direction ${category} en systeme DESIGN.md portable pour tous les skills.`,
systemAtmosphere: (name, category, paletteCount) => `${name} formalise ${category} avec ${paletteCount} couleurs, une hierarchie typographique, des composants et des anti-patterns.`,
craftName: (name) => `Regle de conception ${name}`,
craftSummary: (name) => `Cette regle Open Design definit les standards ${name} pour produire des artefacts coherents, lisibles et livrables.`,
templateName: (name) => `Modele ${name}`,
templateSummary: (name) => `${name} est un modele Live Artifact reutilisable avec point de rendu, donnees d'exemple et structure forkable.`,
pluginTitle: (kind, id) => `Plugin ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin Open Design pour les flux ${kind}. Une fois installe, il fonctionne avec le daemon local et la CLI od${labels.length ? `; portee: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Utilisez ce plugin pour lancer une tache ${kind} et verifier le resultat dans l'espace de travail Open Design local.`,
blogTitle: (topic) => `Guide Open Design : ${topic}`,
blogSummary: (topic) => `Resume localise de ${topic} dans le contexte local-first, BYOK et skills composables d'Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Resume localise</h2><p>Cet article explique comment Open Design relie ${topic}, les artefacts, les skills, les systemes de design et les agents locaux.</p><p>Cette page utilise un fallback i18n. Une traduction complete peut etre fournie via <code>i18n.bodyHtml</code> dans le frontmatter.</p>`,
},
ru: {
skillNoun: 'навык',
systemNoun: 'дизайн-система',
templateNoun: 'шаблон',
craftNoun: 'правило качества',
pluginNoun: 'плагин',
blogNoun: 'статья',
unknownTag: 'категория',
skillDescription: (name, labels) => `${name} — составной навык Open Design для сценариев ${labels.join(', ') || 'дизайн-артефактов'}. Его запускает локальный агент, а правила DESIGN.md переиспользуются между задачами.`,
systemTagline: (name, category) => `${name} превращает направление ${category} в переносимую DESIGN.md дизайн-систему для всех навыков.`,
systemAtmosphere: (name, category, paletteCount) => `${name} описывает ${category}: ${paletteCount} основных цветов, типографику, компоненты и анти-паттерны.`,
craftName: (name) => `Правило качества: ${name}`,
craftSummary: (name) => `Это правило Open Design задает стандарт ${name}, чтобы агент создавал согласованные и пригодные к передаче артефакты.`,
templateName: (name) => `Шаблон ${name}`,
templateSummary: (name) => `${name} — переиспользуемый Live Artifact шаблон с точкой рендера, примером данных и структурой для fork.`,
pluginTitle: (kind, id) => `Плагин ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Плагин Open Design для сценариев ${kind}. После установки доступен локально через daemon и CLI od${labels.length ? `; охват: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Создайте задачу ${kind} с этим плагином и проверьте результат в локальном рабочем пространстве Open Design.`,
blogTitle: (topic) => `Гид Open Design: ${topic}`,
blogSummary: (topic) => `Локализованное резюме о ${topic} и о том, как это связано с local-first, BYOK и составными навыками Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Локализованное резюме</h2><p>Статья объясняет, как Open Design связывает ${topic}, артефакты, навыки, дизайн-системы и локальных агентов.</p><p>Сейчас страница использует i18n fallback. Полный перевод можно задать через <code>i18n.bodyHtml</code> во frontmatter.</p>`,
},
es: {
skillNoun: 'skill',
systemNoun: 'sistema de diseño',
templateNoun: 'plantilla',
craftNoun: 'regla de oficio',
pluginNoun: 'plugin',
blogNoun: 'articulo',
unknownTag: 'categoria',
skillDescription: (name, labels) => `${name} es un skill componible de Open Design para flujos de ${labels.join(', ') || 'artefactos de diseño'}. Lo ejecuta el agente local y reutiliza sistemas DESIGN.md.`,
systemTagline: (name, category) => `${name} convierte la direccion ${category} en un sistema DESIGN.md portable para todos los skills.`,
systemAtmosphere: (name, category, paletteCount) => `${name} expresa ${category} con ${paletteCount} colores base, ritmo tipografico, componentes y anti-patrones.`,
craftName: (name) => `Regla de oficio ${name}`,
craftSummary: (name) => `Esta regla de Open Design define el estandar ${name} para producir artefactos coherentes, legibles y entregables.`,
templateName: (name) => `Plantilla ${name}`,
templateSummary: (name) => `${name} es una plantilla Live Artifact reutilizable con entrada de render, datos de ejemplo y estructura lista para fork.`,
pluginTitle: (kind, id) => `Plugin de ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin de Open Design para flujos de ${kind}. Tras instalarlo, funciona con el daemon local y la CLI od${labels.length ? `; cubre: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Usa este plugin para crear una tarea de ${kind} y revisar el resultado en el workspace local de Open Design.`,
blogTitle: (topic) => `Guia Open Design: ${topic}`,
blogSummary: (topic) => `Resumen localizado sobre ${topic} dentro del flujo local-first, BYOK y de skills componibles de Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Resumen localizado</h2><p>Este articulo explica como Open Design conecta ${topic}, artefactos, skills, sistemas de diseño y agentes locales.</p><p>La pagina usa un fallback i18n; una traduccion completa puede sobrescribirse con <code>i18n.bodyHtml</code> en el frontmatter.</p>`,
},
'pt-br': {
skillNoun: 'skill',
systemNoun: 'sistema de design',
templateNoun: 'modelo',
craftNoun: 'regra de craft',
pluginNoun: 'plugin',
blogNoun: 'artigo',
unknownTag: 'categoria',
skillDescription: (name, labels) => `${name} e um skill componivel do Open Design para fluxos de ${labels.join(', ') || 'artefatos de design'}. Ele roda com o agente local e reutiliza sistemas DESIGN.md.`,
systemTagline: (name, category) => `${name} transforma a direcao ${category} em um sistema DESIGN.md portavel para todos os skills.`,
systemAtmosphere: (name, category, paletteCount) => `${name} traduz ${category} em ${paletteCount} cores principais, tipografia, componentes e anti-padroes.`,
craftName: (name) => `Regra de craft ${name}`,
craftSummary: (name) => `Esta regra do Open Design define o padrao ${name} para gerar artefatos consistentes, legiveis e entregaveis.`,
templateName: (name) => `Modelo ${name}`,
templateSummary: (name) => `${name} e um modelo Live Artifact reutilizavel com entrada de renderizacao, dados de exemplo e estrutura pronta para fork.`,
pluginTitle: (kind, id) => `Plugin de ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin do Open Design para fluxos de ${kind}. Depois de instalado, funciona no daemon local e na CLI od${labels.length ? `; cobre: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Use este plugin para criar uma tarefa de ${kind} e revisar o resultado no workspace local do Open Design.`,
blogTitle: (topic) => `Guia Open Design: ${topic}`,
blogSummary: (topic) => `Resumo localizado sobre ${topic} no fluxo local-first, BYOK e de skills componiveis do Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Resumo localizado</h2><p>Este artigo explica como o Open Design conecta ${topic}, artefatos, skills, sistemas de design e agentes locais.</p><p>A pagina usa um fallback i18n; uma traducao completa pode ser sobrescrita por <code>i18n.bodyHtml</code> no frontmatter.</p>`,
},
it: {
skillNoun: 'skill',
systemNoun: 'sistema di design',
templateNoun: 'modello',
craftNoun: 'regola di craft',
pluginNoun: 'plugin',
blogNoun: 'articolo',
unknownTag: 'categoria',
skillDescription: (name, labels) => `${name} e uno skill componibile di Open Design per flussi ${labels.join(', ') || 'di artefatti design'}. Viene eseguito dall'agente locale e riusa sistemi DESIGN.md.`,
systemTagline: (name, category) => `${name} traduce la direzione ${category} in un sistema DESIGN.md portabile per tutti gli skill.`,
systemAtmosphere: (name, category, paletteCount) => `${name} definisce ${category} con ${paletteCount} colori base, tipografia, componenti e anti-pattern.`,
craftName: (name) => `Regola di craft ${name}`,
craftSummary: (name) => `Questa regola Open Design definisce lo standard ${name} per produrre artefatti coerenti, leggibili e consegnabili.`,
templateName: (name) => `Modello ${name}`,
templateSummary: (name) => `${name} e un modello Live Artifact riutilizzabile con ingresso di rendering, dati di esempio e struttura forkabile.`,
pluginTitle: (kind, id) => `Plugin ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin Open Design per flussi ${kind}. Dopo l'installazione funziona con il daemon locale e la CLI od${labels.length ? `; copre: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Usa questo plugin per creare un task ${kind} e controllare il risultato nel workspace locale Open Design.`,
blogTitle: (topic) => `Guida Open Design: ${topic}`,
blogSummary: (topic) => `Sintesi localizzata di ${topic} nel flusso local-first, BYOK e skill componibili di Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Sintesi localizzata</h2><p>Questo articolo spiega come Open Design collega ${topic}, artefatti, skill, sistemi di design e agenti locali.</p><p>La pagina usa un fallback i18n; una traduzione completa puo essere fornita con <code>i18n.bodyHtml</code> nel frontmatter.</p>`,
},
vi: {
skillNoun: 'skill',
systemNoun: 'he thong thiet ke',
templateNoun: 'mau',
craftNoun: 'quy tac craft',
pluginNoun: 'plugin',
blogNoun: 'bai viet',
unknownTag: 'phan loai',
skillDescription: (name, labels) => `${name} la skill Open Design co the ghep noi cho luong ${labels.join(', ') || 'artifact thiet ke'}. Skill chay voi agent cuc bo va tai su dung cac he DESIGN.md.`,
systemTagline: (name, category) => `${name} bien huong ${category} thanh he DESIGN.md di dong cho moi skill.`,
systemAtmosphere: (name, category, paletteCount) => `${name} mo ta ${category} voi ${paletteCount} mau cot loi, nhip chu, thanh phan va cac mau can tranh.`,
craftName: (name) => `Quy tac craft ${name}`,
craftSummary: (name) => `Quy tac Open Design nay dat chuan ${name} de agent tao artifact nhat quan, de doc va co the ban giao.`,
templateName: (name) => `Mau ${name}`,
templateSummary: (name) => `${name} la mau Live Artifact co the tai su dung, gom diem render, du lieu mau va cau truc co the fork.`,
pluginTitle: (kind, id) => `Plugin ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin Open Design cho luong ${kind}. Sau khi cai dat, plugin chay voi daemon cuc bo va CLI od${labels.length ? `; pham vi: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Dung plugin nay de tao tac vu ${kind} va xem ket qua trong workspace Open Design cuc bo.`,
blogTitle: (topic) => `Huong dan Open Design: ${topic}`,
blogSummary: (topic) => `Tom tat ban dia hoa ve ${topic} trong luong local-first, BYOK va skill co the ghep noi cua Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Tom tat ban dia hoa</h2><p>Bai viet nay giai thich cach Open Design ket noi ${topic}, artifact, skill, he thiet ke va agent cuc bo.</p><p>Trang dang dung fallback i18n; ban dich day du co the ghi de bang <code>i18n.bodyHtml</code> trong frontmatter.</p>`,
},
pl: {
skillNoun: 'skill',
systemNoun: 'system projektowy',
templateNoun: 'szablon',
craftNoun: 'regula craft',
pluginNoun: 'plugin',
blogNoun: 'artykul',
unknownTag: 'kategoria',
skillDescription: (name, labels) => `${name} to komponowalny skill Open Design dla przeplywow ${labels.join(', ') || 'artefaktow designu'}. Dziala z lokalnym agentem i wykorzystuje systemy DESIGN.md.`,
systemTagline: (name, category) => `${name} zamienia kierunek ${category} w przenosny system DESIGN.md dla wszystkich skillow.`,
systemAtmosphere: (name, category, paletteCount) => `${name} opisuje ${category}: ${paletteCount} kolorow, typografie, komponenty i antywzorce.`,
craftName: (name) => `Regula craft ${name}`,
craftSummary: (name) => `Ta regula Open Design definiuje standard ${name}, aby agent tworzyl spojne i gotowe do przekazania artefakty.`,
templateName: (name) => `Szablon ${name}`,
templateSummary: (name) => `${name} to wielorazowy szablon Live Artifact z punktem renderowania, danymi przykladowymi i struktura do forkowania.`,
pluginTitle: (kind, id) => `Plugin ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin Open Design dla przeplywow ${kind}. Po instalacji dziala lokalnie przez daemon i CLI od${labels.length ? `; zakres: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Utworz zadanie ${kind} tym pluginem i sprawdz wynik w lokalnym workspace Open Design.`,
blogTitle: (topic) => `Przewodnik Open Design: ${topic}`,
blogSummary: (topic) => `Zlokalizowane podsumowanie ${topic} w przeplywie local-first, BYOK i komponowalnych skillow Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Zlokalizowane podsumowanie</h2><p>Ten artykul pokazuje, jak Open Design laczy ${topic}, artefakty, skille, systemy projektowe i lokalnych agentow.</p><p>Strona uzywa fallbacku i18n; pelne tlumaczenie mozna podac przez <code>i18n.bodyHtml</code> we frontmatter.</p>`,
},
id: {
skillNoun: 'skill',
systemNoun: 'sistem desain',
templateNoun: 'templat',
craftNoun: 'aturan craft',
pluginNoun: 'plugin',
blogNoun: 'artikel',
unknownTag: 'kategori',
skillDescription: (name, labels) => `${name} adalah skill Open Design yang dapat dikomposisi untuk alur ${labels.join(', ') || 'artifact desain'}. Skill ini berjalan lewat agen lokal dan memakai ulang sistem DESIGN.md.`,
systemTagline: (name, category) => `${name} mengubah arah ${category} menjadi sistem DESIGN.md portabel untuk semua skill.`,
systemAtmosphere: (name, category, paletteCount) => `${name} merumuskan ${category} dengan ${paletteCount} warna inti, tipografi, komponen, dan anti-pola.`,
craftName: (name) => `Aturan craft ${name}`,
craftSummary: (name) => `Aturan Open Design ini menetapkan standar ${name} agar agen menghasilkan artifact yang konsisten, terbaca, dan siap diserahkan.`,
templateName: (name) => `Templat ${name}`,
templateSummary: (name) => `${name} adalah templat Live Artifact yang dapat dipakai ulang, berisi entry render, data contoh, dan struktur yang bisa di-fork.`,
pluginTitle: (kind, id) => `Plugin ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Plugin Open Design untuk alur ${kind}. Setelah dipasang, plugin berjalan di daemon lokal dan CLI od${labels.length ? `; cakupan: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Gunakan plugin ini untuk membuat tugas ${kind} dan memeriksa hasilnya di workspace Open Design lokal.`,
blogTitle: (topic) => `Panduan Open Design: ${topic}`,
blogSummary: (topic) => `Ringkasan lokal tentang ${topic} dalam alur local-first, BYOK, dan skill yang dapat dikomposisi di Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Ringkasan lokal</h2><p>Artikel ini menjelaskan cara Open Design menghubungkan ${topic}, artifact, skill, sistem desain, dan agen lokal.</p><p>Halaman ini memakai fallback i18n; terjemahan lengkap dapat ditimpa lewat <code>i18n.bodyHtml</code> di frontmatter.</p>`,
},
nl: {
skillNoun: 'skill',
systemNoun: 'designsysteem',
templateNoun: 'sjabloon',
craftNoun: 'craftregel',
pluginNoun: 'plugin',
blogNoun: 'artikel',
unknownTag: 'categorie',
skillDescription: (name, labels) => `${name} is een composeerbare Open Design-skill voor ${labels.join(', ') || 'designartefacten'}. De lokale agent voert hem uit en hergebruikt DESIGN.md-systemen.`,
systemTagline: (name, category) => `${name} vertaalt ${category} naar een draagbaar DESIGN.md-designsysteem voor elke skill.`,
systemAtmosphere: (name, category, paletteCount) => `${name} beschrijft ${category} met ${paletteCount} kernkleuren, typografie, componentregels en anti-patronen.`,
craftName: (name) => `Craftregel ${name}`,
craftSummary: (name) => `Deze Open Design-regel definieert ${name}, zodat agenten consistente, leesbare en overdraagbare artefacten maken.`,
templateName: (name) => `Sjabloon ${name}`,
templateSummary: (name) => `${name} is een herbruikbaar Live Artifact-sjabloon met render-ingang, voorbeelddata en een forkbare structuur.`,
pluginTitle: (kind, id) => `${kind}-plugin · ${id}`,
pluginDescription: (kind, labels) => `Open Design-plugin voor ${kind}-workflows. Na installatie werkt hij lokaal via de daemon en od CLI${labels.length ? `; bereik: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Gebruik deze plugin om een ${kind}-taak te maken en het resultaat in de lokale Open Design-workspace te bekijken.`,
blogTitle: (topic) => `Open Design-gids: ${topic}`,
blogSummary: (topic) => `Gelokaliseerde samenvatting van ${topic} binnen de local-first, BYOK en composeerbare skill-workflow van Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Gelokaliseerde samenvatting</h2><p>Dit artikel legt uit hoe Open Design ${topic}, artefacten, skills, designsystemen en lokale agenten verbindt.</p><p>Deze pagina gebruikt een i18n-fallback. Een volledige vertaling kan via <code>i18n.bodyHtml</code> in de frontmatter worden geplaatst.</p>`,
},
ar: {
skillNoun: 'مهارة',
systemNoun: 'نظام تصميم',
templateNoun: 'قالب',
craftNoun: 'قاعدة جودة',
pluginNoun: 'إضافة',
blogNoun: 'مقال',
unknownTag: 'تصنيف',
skillDescription: (name, labels) => `${name} مهارة قابلة للتركيب في Open Design لسير عمل ${labels.join('، ') || 'إنتاج التصميم'}. تعمل مع الوكيل المحلي وتعيد استخدام أنظمة DESIGN.md.`,
systemTagline: (name, category) => `${name} يحول اتجاه ${category} إلى نظام DESIGN.md قابل للنقل لكل المهارات.`,
systemAtmosphere: (name, category, paletteCount) => `${name} يصف ${category} عبر ${paletteCount} ألوان أساسية وإيقاع طباعي وقواعد مكونات وأنماط يجب تجنبها.`,
craftName: (name) => `قاعدة جودة ${name}`,
craftSummary: (name) => `تحدد هذه القاعدة معيار ${name} حتى ينتج الوكيل ملفات متسقة وقابلة للتسليم.`,
templateName: (name) => `قالب ${name}`,
templateSummary: (name) => `${name} قالب Live Artifact قابل لإعادة الاستخدام، مع مدخل عرض وبيانات مثال وبنية قابلة للتفرع.`,
pluginTitle: (kind, id) => `إضافة ${kind} · ${id}`,
pluginDescription: (kind, labels) => `إضافة Open Design لسير عمل ${kind}. بعد التثبيت تعمل محليا عبر daemon و od CLI${labels.length ? `؛ النطاق: ${labels.join('، ')}` : ''}.`,
pluginExample: (kind) => `استخدم هذه الإضافة لإنشاء مهمة ${kind} ومراجعة النتيجة في مساحة عمل Open Design المحلية.`,
blogTitle: (topic) => `دليل Open Design: ${topic}`,
blogSummary: (topic) => `ملخص محلي حول ${topic} ضمن سير Open Design المحلي و BYOK والمهارات القابلة للتركيب.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>ملخص محلي</h2><p>تشرح هذه المقالة كيف يصل Open Design بين ${topic} والملفات والمهارات وأنظمة التصميم والوكلاء المحليين.</p><p>تعرض الصفحة حاليا نصا عبر i18n fallback؛ يمكن توفير ترجمة كاملة عبر <code>i18n.bodyHtml</code> في frontmatter.</p>`,
},
tr: {
skillNoun: 'skill',
systemNoun: 'tasarim sistemi',
templateNoun: 'sablon',
craftNoun: 'craft kurali',
pluginNoun: 'eklenti',
blogNoun: 'yazi',
unknownTag: 'kategori',
skillDescription: (name, labels) => `${name}, ${labels.join(', ') || 'tasarim artifact'} akislarinda kullanilan birlesebilir bir Open Design skillidir. Yerel ajanla calisir ve DESIGN.md sistemlerini yeniden kullanir.`,
systemTagline: (name, category) => `${name}, ${category} yonunu tum skilllerin kullanabilecegi tasinabilir bir DESIGN.md sistemine donusturur.`,
systemAtmosphere: (name, category, paletteCount) => `${name}, ${category} icin ${paletteCount} ana renk, tipografi, bilesen sinirlari ve anti-pattern kurallari tanimlar.`,
craftName: (name) => `${name} craft kurali`,
craftSummary: (name) => `Bu Open Design kurali ${name} standardini belirler; ajanlarin tutarli, okunabilir ve teslim edilebilir artifact uretmesine yardim eder.`,
templateName: (name) => `${name} sablonu`,
templateSummary: (name) => `${name}, render girisi, ornek veri ve fork edilebilir dosya yapisi iceren yeniden kullanilabilir bir Live Artifact sablonudur.`,
pluginTitle: (kind, id) => `${kind} eklentisi · ${id}`,
pluginDescription: (kind, labels) => `${kind} akislari icin Open Design eklentisi. Kurulumdan sonra yerel daemon ve od CLI ile calisir${labels.length ? `; kapsam: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Bu eklentiyle bir ${kind} gorevi olusturun ve sonucu yerel Open Design workspace'inde inceleyin.`,
blogTitle: (topic) => `Open Design rehberi: ${topic}`,
blogSummary: (topic) => `${topic} konusunu Open Design'in local-first, BYOK ve birlesebilir skill akisi baglaminda ozetler.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Yerellestirilmis ozet</h2><p>Bu yazi Open Design'in ${topic}, artifact, skill, tasarim sistemi ve yerel ajanlari nasil bagladigini aciklar.</p><p>Sayfa su anda i18n fallback kullanir. Tam ceviri frontmatter icindeki <code>i18n.bodyHtml</code> ile verilebilir.</p>`,
},
uk: {
skillNoun: 'навичка',
systemNoun: 'дизайн-система',
templateNoun: 'шаблон',
craftNoun: 'правило якості',
pluginNoun: 'плагін',
blogNoun: 'стаття',
unknownTag: 'категорія',
skillDescription: (name, labels) => `${name} — компонована навичка Open Design для сценаріїв ${labels.join(', ') || 'дизайн-артефактів'}. Її запускає локальний агент, а системи DESIGN.md можна перевикористовувати.`,
systemTagline: (name, category) => `${name} перетворює напрям ${category} на переносну DESIGN.md дизайн-систему для всіх навичок.`,
systemAtmosphere: (name, category, paletteCount) => `${name} описує ${category}: ${paletteCount} основних кольорів, типографіку, компоненти й анти-патерни.`,
craftName: (name) => `Правило якості: ${name}`,
craftSummary: (name) => `Це правило Open Design задає стандарт ${name}, щоб агент створював послідовні й готові до передачі артефакти.`,
templateName: (name) => `Шаблон ${name}`,
templateSummary: (name) => `${name} — багаторазовий Live Artifact шаблон із точкою рендеру, прикладом даних і структурою для fork.`,
pluginTitle: (kind, id) => `Плагін ${kind} · ${id}`,
pluginDescription: (kind, labels) => `Плагін Open Design для сценаріїв ${kind}. Після встановлення працює локально через daemon і CLI od${labels.length ? `; охоплення: ${labels.join(', ')}` : ''}.`,
pluginExample: (kind) => `Створіть завдання ${kind} цим плагіном і перевірте результат у локальному workspace Open Design.`,
blogTitle: (topic) => `Гід Open Design: ${topic}`,
blogSummary: (topic) => `Локалізоване резюме про ${topic} у local-first, BYOK і компонованому skill-процесі Open Design.`,
blogBody: (topic, summary) => `<p>${summary}</p><h2>Локалізоване резюме</h2><p>Стаття пояснює, як Open Design поєднує ${topic}, артефакти, навички, дизайн-системи й локальних агентів.</p><p>Зараз сторінка використовує i18n fallback. Повний переклад можна задати через <code>i18n.bodyHtml</code> у frontmatter.</p>`,
},
};
const TAXONOMY_TERMS: Record<string, Partial<Record<LandingLocaleCode, string>>> = {
prototype: { zh: '原型', 'zh-tw': '原型', ja: 'プロトタイプ', ko: '프로토타입', de: 'Prototyp', fr: 'prototype', ru: 'прототип', es: 'prototipo', 'pt-br': 'prototipo', it: 'prototipo', vi: 'nguyen mau', pl: 'prototyp', id: 'prototipe', nl: 'prototype', ar: 'نموذج أولي', tr: 'prototip', uk: 'прототип' },
template: { zh: '模板', 'zh-tw': '模板', ja: 'テンプレート', ko: '템플릿', de: 'Vorlage', fr: 'modele', ru: 'шаблон', es: 'plantilla', 'pt-br': 'modelo', it: 'modello', vi: 'mau', pl: 'szablon', id: 'templat', nl: 'sjabloon', ar: 'قالب', tr: 'sablon', uk: 'шаблон' },
deck: { zh: '演示文稿', 'zh-tw': '簡報', ja: 'スライド', ko: '슬라이드', de: 'Deck', fr: 'presentation', ru: 'презентация', es: 'presentacion', 'pt-br': 'apresentacao', it: 'presentazione', vi: 'slide', pl: 'prezentacja', id: 'presentasi', nl: 'presentatie', ar: 'عرض تقديمي', tr: 'sunum', uk: 'презентація' },
image: { zh: '图像', 'zh-tw': '影像', ja: '画像', ko: '이미지', de: 'Bild', fr: 'image', ru: 'изображение', es: 'imagen', 'pt-br': 'imagem', it: 'immagine', vi: 'hinh anh', pl: 'obraz', id: 'gambar', nl: 'afbeelding', ar: 'صورة', tr: 'gorsel', uk: 'зображення' },
video: { zh: '视频', 'zh-tw': '影片', ja: '動画', ko: '비디오', de: 'Video', fr: 'video', ru: 'видео', es: 'video', 'pt-br': 'video', it: 'video', vi: 'video', pl: 'wideo', id: 'video', nl: 'video', ar: 'فيديو', tr: 'video', uk: 'відео' },
audio: { zh: '音频', 'zh-tw': '音訊', ja: '音声', ko: '오디오', de: 'Audio', fr: 'audio', ru: 'аудио', es: 'audio', 'pt-br': 'audio', it: 'audio', vi: 'am thanh', pl: 'audio', id: 'audio', nl: 'audio', ar: 'صوت', tr: 'ses', uk: 'аудіо' },
utility: { zh: '工具', 'zh-tw': '工具', ja: 'ユーティリティ', ko: '유틸리티', de: 'Werkzeug', fr: 'outil', ru: 'утилита', es: 'utilidad', 'pt-br': 'utilitario', it: 'utility', vi: 'tien ich', pl: 'narzedzie', id: 'utilitas', nl: 'hulpmiddel', ar: 'أداة', tr: 'arac', uk: 'утиліта' },
design: { zh: '设计', 'zh-tw': '設計', ja: 'デザイン', ko: '디자인', de: 'Design', fr: 'design', ru: 'дизайн', es: 'diseño', 'pt-br': 'design', it: 'design', vi: 'thiet ke', pl: 'design', id: 'desain', nl: 'design', ar: 'تصميم', tr: 'tasarim', uk: 'дизайн' },
marketing: { zh: '营销', 'zh-tw': '行銷', ja: 'マーケティング', ko: '마케팅', de: 'Marketing', fr: 'marketing', ru: 'маркетинг', es: 'marketing', 'pt-br': 'marketing', it: 'marketing', vi: 'marketing', pl: 'marketing', id: 'pemasaran', nl: 'marketing', ar: 'تسويق', tr: 'pazarlama', uk: 'маркетинг' },
operations: { zh: '运营', 'zh-tw': '營運', ja: '運用', ko: '운영', de: 'Betrieb', fr: 'operations', ru: 'операции', es: 'operaciones', 'pt-br': 'operacoes', it: 'operazioni', vi: 'van hanh', pl: 'operacje', id: 'operasi', nl: 'operaties', ar: 'عمليات', tr: 'operasyon', uk: 'операції' },
product: { zh: '产品', 'zh-tw': '產品', ja: 'プロダクト', ko: '제품', de: 'Produkt', fr: 'produit', ru: 'продукт', es: 'producto', 'pt-br': 'produto', it: 'prodotto', vi: 'san pham', pl: 'produkt', id: 'produk', nl: 'product', ar: 'منتج', tr: 'urun', uk: 'продукт' },
personal: { zh: '个人', 'zh-tw': '個人', ja: '個人', ko: '개인', de: 'Persoenlich', fr: 'personnel', ru: 'личное', es: 'personal', 'pt-br': 'pessoal', it: 'personale', vi: 'ca nhan', pl: 'osobiste', id: 'personal', nl: 'persoonlijk', ar: 'شخصي', tr: 'kisisel', uk: 'особисте' },
finance: { zh: '金融', 'zh-tw': '金融', ja: '金融', ko: '금융', de: 'Finanzen', fr: 'finance', ru: 'финансы', es: 'finanzas', 'pt-br': 'financas', it: 'finanza', vi: 'tai chinh', pl: 'finanse', id: 'keuangan', nl: 'financien', ar: 'مالية', tr: 'finans', uk: 'фінанси' },
docs: { zh: '文档', 'zh-tw': '文件', ja: 'ドキュメント', ko: '문서', de: 'Dokumente', fr: 'documents', ru: 'документы', es: 'documentos', 'pt-br': 'documentos', it: 'documenti', vi: 'tai lieu', pl: 'dokumenty', id: 'dokumen', nl: 'documenten', ar: 'مستندات', tr: 'belgeler', uk: 'документи' },
'code-migration': { zh: '代码迁移', 'zh-tw': '程式碼遷移', ja: 'コードマイグレーション', ko: '코드 마이그레이션', de: 'Code-Migration', fr: 'Migration de code', ru: 'Миграция кода', es: 'Migración de código', 'pt-br': 'Migração de código', it: 'Migrazione codice', vi: 'Di trú mã', pl: 'Migracja kodu', id: 'Migrasi kode', nl: 'Code-migratie', ar: 'ترحيل الكود', tr: 'Kod taşıma', uk: 'Міграція коду' },
creator: { zh: '创作者', 'zh-tw': '創作者', ja: 'クリエイター', ko: '크리에이터', de: 'Creator', fr: 'Créateur', ru: 'Создатель', es: 'Creador', 'pt-br': 'Criador', it: 'Creator', vi: 'Người sáng tạo', pl: 'Twórca', id: 'Pembuat', nl: 'Maker', ar: 'المُنشئ', tr: 'Yaratıcı', uk: 'Творець' },
critique: { zh: '评审', 'zh-tw': '評審', ja: 'クリティーク', ko: '크리틱', de: 'Kritik', fr: 'Critique', ru: 'Критика', es: 'Crítica', 'pt-br': 'Crítica', it: 'Critica', vi: 'Phản biện', pl: 'Krytyka', id: 'Kritik', nl: 'Kritiek', ar: 'نقد', tr: 'Eleştiri', uk: 'Критика' },
'default-router': { zh: '默认路由', 'zh-tw': '預設路由', ja: 'デフォルトルーター', ko: '기본 라우터', de: 'Standard-Router', fr: 'Router par défaut', ru: 'Маршрутизатор по умолчанию', es: 'Enrutador por defecto', 'pt-br': 'Roteador padrão', it: 'Router predefinito', vi: 'Router mặc định', pl: 'Router domyślny', id: 'Router default', nl: 'Standaardrouter', ar: 'الموجّه الافتراضي', tr: 'Varsayılan yönlendirici', uk: 'Маршрутизатор за замовчуванням' },
'design-refine': { zh: '设计精修', 'zh-tw': '設計精修', ja: 'デザイン精緻化', ko: '디자인 정교화', de: 'Design-Refinement', fr: 'Affinage du design', ru: 'Доводка дизайна', es: 'Refinamiento de diseño', 'pt-br': 'Refino de design', it: 'Affinamento design', vi: 'Tinh chỉnh thiết kế', pl: 'Dopracowanie designu', id: 'Penyempurnaan desain', nl: 'Designverfijning', ar: 'تحسين التصميم', tr: 'Tasarım rafinajı', uk: 'Доопрацювання дизайну' },
'design-system': { zh: '设计系统', 'zh-tw': '設計系統', ja: 'デザインシステム', ko: '디자인 시스템', de: 'Designsystem', fr: 'Design system', ru: 'Дизайн-система', es: 'Sistema de diseño', 'pt-br': 'Design system', it: 'Design system', vi: 'Hệ thống thiết kế', pl: 'System designu', id: 'Sistem desain', nl: 'Designsysteem', ar: 'نظام التصميم', tr: 'Tasarım sistemi', uk: 'Дизайн-система' },
discovery: { zh: '探索', 'zh-tw': '探索', ja: 'ディスカバリー', ko: '디스커버리', de: 'Discovery', fr: 'Découverte', ru: 'Исследование', es: 'Descubrimiento', 'pt-br': 'Descoberta', it: 'Scoperta', vi: 'Khám phá', pl: 'Odkrywanie', id: 'Penemuan', nl: 'Discovery', ar: 'اكتشاف', tr: 'Keşif', uk: 'Дослідження' },
'downstream-export': { zh: '下游导出', 'zh-tw': '下游匯出', ja: 'ダウンストリームエクスポート', ko: '다운스트림 내보내기', de: 'Downstream-Export', fr: 'Export en aval', ru: 'Экспорт вниз по потоку', es: 'Exportación downstream', 'pt-br': 'Exportação downstream', it: 'Export downstream', vi: 'Xuất downstream', pl: 'Eksport downstream', id: 'Ekspor downstream', nl: 'Downstream-export', ar: 'تصدير لاحق', tr: 'Downstream dışa aktarma', uk: 'Downstream експорт' },
edit: { zh: '编辑', 'zh-tw': '編輯', ja: '編集', ko: '편집', de: 'Bearbeiten', fr: 'Édition', ru: 'Редактирование', es: 'Editar', 'pt-br': 'Editar', it: 'Modifica', vi: 'Chỉnh sửa', pl: 'Edycja', id: 'Sunting', nl: 'Bewerken', ar: 'تحرير', tr: 'Düzenle', uk: 'Редагування' },
education: { zh: '教育', 'zh-tw': '教育', ja: '教育', ko: '교육', de: 'Bildung', fr: 'Éducation', ru: 'Образование', es: 'Educación', 'pt-br': 'Educação', it: 'Istruzione', vi: 'Giáo dục', pl: 'Edukacja', id: 'Pendidikan', nl: 'Onderwijs', ar: 'تعليم', tr: 'Eğitim', uk: 'Освіта' },
engineering: { zh: '工程', 'zh-tw': '工程', ja: 'エンジニアリング', ko: '엔지니어링', de: 'Engineering', fr: 'Ingénierie', ru: 'Инженерия', es: 'Ingeniería', 'pt-br': 'Engenharia', it: 'Ingegneria', vi: 'Kỹ thuật', pl: 'Inżynieria', id: 'Teknik', nl: 'Engineering', ar: 'هندسة', tr: 'Mühendislik', uk: 'Інженерія' },
export: { zh: '导出', 'zh-tw': '匯出', ja: 'エクスポート', ko: '내보내기', de: 'Export', fr: 'Export', ru: 'Экспорт', es: 'Exportar', 'pt-br': 'Exportar', it: 'Esporta', vi: 'Xuất', pl: 'Eksport', id: 'Ekspor', nl: 'Exporteren', ar: 'تصدير', tr: 'Dışa aktar', uk: 'Експорт' },
extract: { zh: '提取', 'zh-tw': '擷取', ja: '抽出', ko: '추출', de: 'Extrahieren', fr: 'Extraire', ru: 'Извлечение', es: 'Extraer', 'pt-br': 'Extrair', it: 'Estrai', vi: 'Trích xuất', pl: 'Wyodrębnij', id: 'Ekstrak', nl: 'Extraheren', ar: 'استخراج', tr: ıkar', uk: 'Витягнення' },
'figma-migration': { zh: 'Figma 迁移', 'zh-tw': 'Figma 遷移', ja: 'Figma マイグレーション', ko: 'Figma 마이그레이션', de: 'Figma-Migration', fr: 'Migration Figma', ru: 'Миграция Figma', es: 'Migración Figma', 'pt-br': 'Migração do Figma', it: 'Migrazione Figma', vi: 'Di trú Figma', pl: 'Migracja Figma', id: 'Migrasi Figma', nl: 'Figma-migratie', ar: 'ترحيل Figma', tr: 'Figma taşıma', uk: 'Міграція Figma' },
general: { zh: '通用', 'zh-tw': '通用', ja: '汎用', ko: '일반', de: 'Allgemein', fr: 'Général', ru: 'Общее', es: 'General', 'pt-br': 'Geral', it: 'Generale', vi: 'Tổng quát', pl: 'Ogólne', id: 'Umum', nl: 'Algemeen', ar: 'عام', tr: 'Genel', uk: 'Загальне' },
handoff: { zh: '交付', 'zh-tw': '交付', ja: 'ハンドオフ', ko: '핸드오프', de: 'Übergabe', fr: 'Transfert', ru: 'Передача', es: 'Entrega', 'pt-br': 'Handoff', it: 'Handoff', vi: 'Bàn giao', pl: 'Handoff', id: 'Handoff', nl: 'Overdracht', ar: 'تسليم', tr: 'Devir', uk: 'Передача' },
healthcare: { zh: '医疗', 'zh-tw': '醫療', ja: '医療', ko: '의료', de: 'Gesundheit', fr: 'Santé', ru: 'Здравоохранение', es: 'Salud', 'pt-br': 'Saúde', it: 'Sanità', vi: 'Y tế', pl: 'Opieka zdrowotna', id: 'Kesehatan', nl: 'Zorg', ar: 'الرعاية الصحية', tr: 'Sağlık', uk: 'Охорона здоровʼя' },
hr: { zh: '人力资源', 'zh-tw': '人力資源', ja: '人事', ko: '인사', de: 'HR', fr: 'RH', ru: 'HR', es: 'RR. HH.', 'pt-br': 'RH', it: 'Risorse umane', vi: 'Nhân sự', pl: 'HR', id: 'SDM', nl: 'HR', ar: 'الموارد البشرية', tr: 'İK', uk: 'Кадри' },
import: { zh: '导入', 'zh-tw': '匯入', ja: 'インポート', ko: '가져오기', de: 'Import', fr: 'Import', ru: 'Импорт', es: 'Importar', 'pt-br': 'Importar', it: 'Importa', vi: 'Nhập', pl: 'Import', id: 'Impor', nl: 'Importeren', ar: 'استيراد', tr: 'İçe aktar', uk: 'Імпорт' },
knowledge: { zh: '知识', 'zh-tw': '知識', ja: 'ナレッジ', ko: '지식', de: 'Wissen', fr: 'Connaissance', ru: 'Знания', es: 'Conocimiento', 'pt-br': 'Conhecimento', it: 'Conoscenza', vi: 'Tri thức', pl: 'Wiedza', id: 'Pengetahuan', nl: 'Kennis', ar: 'معرفة', tr: 'Bilgi', uk: 'Знання' },
live: { zh: '实时', 'zh-tw': '即時', ja: 'ライブ', ko: '라이브', de: 'Live', fr: 'Live', ru: 'Прямой эфир', es: 'En vivo', 'pt-br': 'Ao vivo', it: 'Live', vi: 'Trực tiếp', pl: 'Live', id: 'Live', nl: 'Live', ar: 'مباشر', tr: 'Canlı', uk: 'Наживо' },
'live-artifacts': { zh: '实时产物', 'zh-tw': '即時產物', ja: 'ライブ成果物', ko: '라이브 산출물', de: 'Live-Artefakte', fr: 'Artefacts en direct', ru: 'Живые артефакты', es: 'Artefactos en vivo', 'pt-br': 'Artefatos ao vivo', it: 'Artefatti live', vi: 'Artifact trực tiếp', pl: 'Artefakty live', id: 'Artefak live', nl: 'Live artefacten', ar: 'عناصر مباشرة', tr: 'Canlı çıktılar', uk: 'Живі артефакти' },
'media-generation': { zh: '媒体生成', 'zh-tw': '媒體生成', ja: 'メディア生成', ko: '미디어 생성', de: 'Medienerzeugung', fr: 'Génération de médias', ru: 'Генерация медиа', es: 'Generación de medios', 'pt-br': 'Geração de mídia', it: 'Generazione media', vi: 'Tạo media', pl: 'Generowanie mediów', id: 'Pembuatan media', nl: 'Mediageneratie', ar: 'توليد الوسائط', tr: 'Medya üretimi', uk: 'Генерація медіа' },
'new-generation': { zh: '新生成', 'zh-tw': '新生成', ja: '新規生成', ko: '새로 생성', de: 'Neuerzeugung', fr: 'Nouvelle génération', ru: 'Новая генерация', es: 'Nueva generación', 'pt-br': 'Nova geração', it: 'Nuova generazione', vi: 'Tạo mới', pl: 'Nowe tworzenie', id: 'Pembuatan baru', nl: 'Nieuwe generatie', ar: 'إنشاء جديد', tr: 'Yeni üretim', uk: 'Нова генерація' },
operation: { zh: '运维', 'zh-tw': '營運', ja: '運用', ko: '운영', de: 'Betrieb', fr: 'Opération', ru: 'Операции', es: 'Operación', 'pt-br': 'Operação', it: 'Operazione', vi: 'Vận hành', pl: 'Operacja', id: 'Operasi', nl: 'Operatie', ar: 'عملية', tr: 'Operasyon', uk: 'Операція' },
orbit: { zh: '轨道', 'zh-tw': '軌道', ja: 'オービット', ko: '오빗', de: 'Orbit', fr: 'Orbit', ru: 'Orbit', es: 'Orbit', 'pt-br': 'Orbit', it: 'Orbit', vi: 'Orbit', pl: 'Orbit', id: 'Orbit', nl: 'Orbit', ar: 'مدار', tr: 'Yörünge', uk: 'Орбіта' },
planning: { zh: '规划', 'zh-tw': '規劃', ja: '計画', ko: '계획', de: 'Planung', fr: 'Planification', ru: 'Планирование', es: 'Planificación', 'pt-br': 'Planejamento', it: 'Pianificazione', vi: 'Lập kế hoạch', pl: 'Planowanie', id: 'Perencanaan', nl: 'Planning', ar: 'تخطيط', tr: 'Planlama', uk: 'Планування' },
'plugin-authoring': { zh: '插件编写', 'zh-tw': '外掛編寫', ja: 'プラグイン作成', ko: '플러그인 작성', de: 'Plugin-Erstellung', fr: 'Création de plugin', ru: 'Создание плагина', es: 'Creación de plugin', 'pt-br': 'Criação de plugin', it: 'Creazione plugin', vi: 'Tạo plugin', pl: 'Tworzenie wtyczki', id: 'Pembuatan plugin', nl: 'Plug-in maken', ar: 'تأليف الإضافة', tr: 'Eklenti yazma', uk: 'Створення плагіна' },
'plugin-sharing': { zh: '插件分享', 'zh-tw': '外掛分享', ja: 'プラグイン共有', ko: '플러그인 공유', de: 'Plugin-Sharing', fr: 'Partage de plugin', ru: 'Шаринг плагина', es: 'Compartir plugin', 'pt-br': 'Compartilhamento de plugin', it: 'Condivisione plugin', vi: 'Chia sẻ plugin', pl: 'Udostępnianie wtyczki', id: 'Berbagi plugin', nl: 'Plug-in delen', ar: 'مشاركة الإضافة', tr: 'Eklenti paylaşımı', uk: 'Поширення плагіна' },
refine: { zh: '精修', 'zh-tw': '精修', ja: '精緻化', ko: '정교화', de: 'Verfeinern', fr: 'Affiner', ru: 'Доводка', es: 'Refinar', 'pt-br': 'Refinar', it: 'Affinare', vi: 'Tinh chỉnh', pl: 'Dopracuj', id: 'Sempurnakan', nl: 'Verfijnen', ar: 'تحسين', tr: 'Rafine et', uk: 'Доопрацювати' },
review: { zh: '评审', 'zh-tw': '評審', ja: 'レビュー', ko: '리뷰', de: 'Review', fr: 'Revue', ru: 'Ревью', es: 'Revisión', 'pt-br': 'Revisão', it: 'Revisione', vi: 'Đánh giá', pl: 'Recenzja', id: 'Tinjau', nl: 'Review', ar: 'مراجعة', tr: 'İnceleme', uk: 'Огляд' },
sales: { zh: '销售', 'zh-tw': '銷售', ja: 'セールス', ko: '세일즈', de: 'Vertrieb', fr: 'Ventes', ru: 'Продажи', es: 'Ventas', 'pt-br': 'Vendas', it: 'Vendite', vi: 'Bán hàng', pl: 'Sprzedaż', id: 'Penjualan', nl: 'Verkoop', ar: 'مبيعات', tr: 'Satış', uk: 'Продажі' },
scenario: { zh: '场景', 'zh-tw': '場景', ja: 'シナリオ', ko: '시나리오', de: 'Szenario', fr: 'Scénario', ru: 'Сценарий', es: 'Escenario', 'pt-br': 'Cenário', it: 'Scenario', vi: 'Kịch bản', pl: 'Scenariusz', id: 'Skenario', nl: 'Scenario', ar: 'سيناريو', tr: 'Senaryo', uk: 'Сценарій' },
support: { zh: '支持', 'zh-tw': '支援', ja: 'サポート', ko: '지원', de: 'Support', fr: 'Support', ru: 'Поддержка', es: 'Soporte', 'pt-br': 'Suporte', it: 'Supporto', vi: 'Hỗ trợ', pl: 'Wsparcie', id: 'Dukungan', nl: 'Support', ar: 'دعم', tr: 'Destek', uk: 'Підтримка' },
'token-map': { zh: 'Token 映射', 'zh-tw': 'Token 對應', ja: 'トークンマップ', ko: '토큰 맵', de: 'Token-Map', fr: 'Carte de tokens', ru: 'Карта токенов', es: 'Mapa de tokens', 'pt-br': 'Mapa de tokens', it: 'Mappa token', vi: 'Bản đồ token', pl: 'Mapa tokenów', id: 'Peta token', nl: 'Token-map', ar: 'خريطة الرموز', tr: 'Token haritası', uk: 'Карта токенів' },
'tune-collab': { zh: '调优协作', 'zh-tw': '調優協作', ja: 'チューン協作', ko: '튜닝 협업', de: 'Tuning-Collab', fr: 'Collab de tuning', ru: 'Тюнинг-коллаб', es: 'Colaboración de ajuste', 'pt-br': 'Colaboração de ajuste', it: 'Collab di tuning', vi: 'Hợp tác tinh chỉnh', pl: 'Tuning collab', id: 'Kolaborasi tuning', nl: 'Tuning-collab', ar: 'تعاون الضبط', tr: 'Tuning iş birliği', uk: 'Тюнінг-співпраця' },
validation: { zh: '验证', 'zh-tw': '驗證', ja: '検証', ko: '검증', de: 'Validierung', fr: 'Validation', ru: 'Валидация', es: 'Validación', 'pt-br': 'Validação', it: 'Validazione', vi: 'Xác nhận', pl: 'Walidacja', id: 'Validasi', nl: 'Validatie', ar: 'تحقق', tr: 'Doğrulama', uk: 'Валідація' },
};
const CRAFT_LABELS: Record<string, Partial<Record<LandingLocaleCode, string>>> = {
color: { zh: '色彩', 'zh-tw': '色彩', ja: 'カラー', ko: '색상', de: 'Farbe', fr: 'couleur', ru: 'цвет', es: 'color', 'pt-br': 'cor', it: 'colore', vi: 'mau sac', pl: 'kolor', id: 'warna', nl: 'kleur', ar: 'اللون', tr: 'renk', uk: 'колір' },
typography: { zh: '排版', 'zh-tw': '排版', ja: 'タイポグラフィ', ko: '타이포그래피', de: 'Typografie', fr: 'typographie', ru: 'типографика', es: 'tipografia', 'pt-br': 'tipografia', it: 'tipografia', vi: 'kieu chu', pl: 'typografia', id: 'tipografi', nl: 'typografie', ar: 'الطباعة', tr: 'tipografi', uk: 'типографіка' },
'rtl-and-bidi': { zh: 'RTL 与双向文本', 'zh-tw': 'RTL 與雙向文字', ja: 'RTL と双方向テキスト', ko: 'RTL 및 양방향 텍스트', de: 'RTL und bidirektionaler Text', fr: 'RTL et texte bidirectionnel', ru: 'RTL и двунаправленный текст', es: 'RTL y texto bidireccional', 'pt-br': 'RTL e texto bidirecional', it: 'RTL e testo bidirezionale', vi: 'RTL va van ban hai chieu', pl: 'RTL i tekst dwukierunkowy', id: 'RTL dan teks dua arah', nl: 'RTL en bidirectionele tekst', ar: 'النص من اليمين والاتجاه المزدوج', tr: 'RTL ve cift yonlu metin', uk: 'RTL і двонапрямний текст' },
};
const CATEGORY_LABELS: Record<string, Partial<Record<LandingLocaleCode, string>>> = {
'ai & llm': { zh: 'AI 与大模型', 'zh-tw': 'AI 與大模型', ja: 'AI と LLM', ko: 'AI 및 LLM', de: 'KI und LLM', fr: 'IA et LLM', ru: 'AI и LLM', es: 'IA y LLM', 'pt-br': 'IA e LLM', it: 'IA e LLM', vi: 'AI va LLM', pl: 'AI i LLM', id: 'AI dan LLM', nl: 'AI en LLM', ar: 'الذكاء الاصطناعي والنماذج اللغوية', tr: 'AI ve LLM', uk: 'AI та LLM' },
'developer tools': { zh: '开发者工具', 'zh-tw': '開發者工具', ja: '開発者ツール', ko: '개발자 도구', de: 'Entwicklerwerkzeuge', fr: 'outils developpeur', ru: 'инструменты разработчика', es: 'herramientas de desarrollo', 'pt-br': 'ferramentas de desenvolvimento', it: 'strumenti per sviluppatori', vi: 'cong cu lap trinh', pl: 'narzedzia developerskie', id: 'alat developer', nl: 'ontwikkelaarstools', ar: 'أدوات المطورين', tr: 'gelistirici araclari', uk: 'інструменти розробника' },
'productivity & saas': { zh: '效率与 SaaS', 'zh-tw': '效率與 SaaS', ja: '生産性と SaaS', ko: '생산성 및 SaaS', de: 'Produktivitaet und SaaS', fr: 'productivite et SaaS', ru: 'продуктивность и SaaS', es: 'productividad y SaaS', 'pt-br': 'produtividade e SaaS', it: 'produttivita e SaaS', vi: 'nang suat va SaaS', pl: 'produktywnosc i SaaS', id: 'produktivitas dan SaaS', nl: 'productiviteit en SaaS', ar: 'الإنتاجية وSaaS', tr: 'uretkenlik ve SaaS', uk: 'продуктивність і SaaS' },
'design & creative': { zh: '设计与创意', 'zh-tw': '設計與創意', ja: 'デザインとクリエイティブ', ko: '디자인 및 크리에이티브', de: 'Design und Kreativitaet', fr: 'design et creation', ru: 'дизайн и креатив', es: 'diseño y creatividad', 'pt-br': 'design e criatividade', it: 'design e creativita', vi: 'thiet ke va sang tao', pl: 'design i kreatywnosc', id: 'desain dan kreatif', nl: 'design en creativiteit', ar: 'التصميم والإبداع', tr: 'tasarim ve yaraticilik', uk: 'дизайн і креатив' },
'3d-shaders': { zh: '3D 着色器', 'zh-tw': '3D 著色器', ja: '3D シェーダー', ko: '3D 셰이더', de: '3D-Shader', fr: 'Shaders 3D', ru: '3D-шейдеры', es: 'Shaders 3D', 'pt-br': 'Shaders 3D', it: 'Shader 3D', vi: 'Shader 3D', pl: 'Shadery 3D', id: 'Shader 3D', nl: '3D-shaders', ar: 'مظللات 3D', tr: '3D shader', uk: '3D-шейдери' },
'animation-motion': { zh: '动效', 'zh-tw': '動效', ja: 'アニメーション・モーション', ko: '애니메이션 모션', de: 'Animation und Motion', fr: 'Animation et motion', ru: 'Анимация и motion', es: 'Animación y motion', 'pt-br': 'Animação e motion', it: 'Animazione e motion', vi: 'Hoạt hình và motion', pl: 'Animacja i motion', id: 'Animasi dan motion', nl: 'Animatie en motion', ar: 'الحركة والتحريك', tr: 'Animasyon ve motion', uk: 'Анімація та motion' },
'audio-music': { zh: '音频与音乐', 'zh-tw': '音訊與音樂', ja: 'オーディオと音楽', ko: '오디오와 음악', de: 'Audio und Musik', fr: 'Audio et musique', ru: 'Аудио и музыка', es: 'Audio y música', 'pt-br': 'Áudio e música', it: 'Audio e musica', vi: 'Âm thanh và nhạc', pl: 'Audio i muzyka', id: 'Audio dan musik', nl: 'Audio en muziek', ar: 'الصوت والموسيقى', tr: 'Ses ve müzik', uk: 'Аудіо та музика' },
'creative-direction': { zh: '创意指导', 'zh-tw': '創意指導', ja: 'クリエイティブディレクション', ko: '크리에이티브 디렉션', de: 'Creative Direction', fr: 'Direction créative', ru: 'Креативное руководство', es: 'Dirección creativa', 'pt-br': 'Direção criativa', it: 'Direzione creativa', vi: 'Chỉ đạo sáng tạo', pl: 'Kierunek kreatywny', id: 'Arah kreatif', nl: 'Creative direction', ar: 'الإخراج الإبداعي', tr: 'Kreatif yönlendirme', uk: 'Креативне керівництво' },
'design-systems': { zh: '设计系统', 'zh-tw': '設計系統', ja: 'デザインシステム', ko: '디자인 시스템', de: 'Designsysteme', fr: 'Design systems', ru: 'Дизайн-системы', es: 'Sistemas de diseño', 'pt-br': 'Design systems', it: 'Design system', vi: 'Hệ thống thiết kế', pl: 'Systemy designu', id: 'Sistem desain', nl: 'Designsystemen', ar: 'أنظمة التصميم', tr: 'Tasarım sistemleri', uk: 'Дизайн-системи' },
diagrams: { zh: '图表', 'zh-tw': '圖表', ja: 'ダイアグラム', ko: '다이어그램', de: 'Diagramme', fr: 'Diagrammes', ru: 'Диаграммы', es: 'Diagramas', 'pt-br': 'Diagramas', it: 'Diagrammi', vi: 'Sơ đồ', pl: 'Diagramy', id: 'Diagram', nl: 'Diagrammen', ar: 'مخططات', tr: 'Diyagramlar', uk: 'Діаграми' },
documents: { zh: '文档', 'zh-tw': '文件', ja: 'ドキュメント', ko: '문서', de: 'Dokumente', fr: 'Documents', ru: 'Документы', es: 'Documentos', 'pt-br': 'Documentos', it: 'Documenti', vi: 'Tài liệu', pl: 'Dokumenty', id: 'Dokumen', nl: 'Documenten', ar: 'مستندات', tr: 'Belgeler', uk: 'Документи' },
figma: { zh: 'Figma', 'zh-tw': 'Figma', ja: 'Figma', ko: 'Figma', de: 'Figma', fr: 'Figma', ru: 'Figma', es: 'Figma', 'pt-br': 'Figma', it: 'Figma', vi: 'Figma', pl: 'Figma', id: 'Figma', nl: 'Figma', ar: 'Figma', tr: 'Figma', uk: 'Figma' },
'image-generation': { zh: '图像生成', 'zh-tw': '影像生成', ja: '画像生成', ko: '이미지 생성', de: 'Bildgenerierung', fr: 'Génération dimages', ru: 'Генерация изображений', es: 'Generación de imágenes', 'pt-br': 'Geração de imagens', it: 'Generazione immagini', vi: 'Tạo hình ảnh', pl: 'Generowanie obrazów', id: 'Pembuatan gambar', nl: 'Beeldgeneratie', ar: 'توليد الصور', tr: 'Görsel üretimi', uk: 'Генерація зображень' },
'marketing-creative': { zh: '营销创意', 'zh-tw': '行銷創意', ja: 'マーケティング・クリエイティブ', ko: '마케팅 크리에이티브', de: 'Marketing-Creative', fr: 'Créatif marketing', ru: 'Креатив маркетинга', es: 'Creatividad de marketing', 'pt-br': 'Criativo de marketing', it: 'Creativo marketing', vi: 'Sáng tạo marketing', pl: 'Kreacja marketingowa', id: 'Kreatif pemasaran', nl: 'Marketing creative', ar: 'إبداع التسويق', tr: 'Pazarlama kreatif', uk: 'Маркетинг креатив' },
screenshots: { zh: '截图', 'zh-tw': '截圖', ja: 'スクリーンショット', ko: '스크린샷', de: 'Screenshots', fr: 'Captures décran', ru: 'Скриншоты', es: 'Capturas de pantalla', 'pt-br': 'Capturas de tela', it: 'Screenshot', vi: 'Ảnh chụp màn hình', pl: 'Zrzuty ekranu', id: 'Tangkapan layar', nl: 'Schermafbeeldingen', ar: 'لقطات الشاشة', tr: 'Ekran görüntüleri', uk: 'Скриншоти' },
slides: { zh: '幻灯片', 'zh-tw': '簡報', ja: 'スライド', ko: '슬라이드', de: 'Slides', fr: 'Slides', ru: 'Слайды', es: 'Diapositivas', 'pt-br': 'Slides', it: 'Slide', vi: 'Slide', pl: 'Slajdy', id: 'Slide', nl: 'Slides', ar: 'شرائح', tr: 'Slaytlar', uk: 'Слайди' },
'video-generation': { zh: '视频生成', 'zh-tw': '影片生成', ja: '動画生成', ko: '비디오 생성', de: 'Videoerzeugung', fr: 'Génération de vidéos', ru: 'Генерация видео', es: 'Generación de video', 'pt-br': 'Geração de vídeo', it: 'Generazione video', vi: 'Tạo video', pl: 'Generowanie wideo', id: 'Pembuatan video', nl: 'Videogeneratie', ar: 'توليد الفيديو', tr: 'Video üretimi', uk: 'Генерація відео' },
'web-artifacts': { zh: 'Web 产物', 'zh-tw': 'Web 產物', ja: 'Web 成果物', ko: 'Web 산출물', de: 'Web-Artefakte', fr: 'Artefacts web', ru: 'Веб-артефакты', es: 'Artefactos web', 'pt-br': 'Artefatos web', it: 'Artefatti web', vi: 'Artifact web', pl: 'Artefakty web', id: 'Artefak web', nl: 'Web-artefacten', ar: 'عناصر الويب', tr: 'Web çıktıları', uk: 'Веб-артефакти' },
};
const normalizeTerm = (value: string) => value.trim().toLowerCase();
const copyFor = (locale: LandingLocaleCode): ContentCopy | undefined =>
locale === DEFAULT_LOCALE ? undefined : CONTENT_COPY[locale];
const compactId = (value: string) =>
value
.split('/')
.at(-1)!
.replace(/^example-/, '')
.replace(/^design-system-/, '')
.replace(/^video-template-/, '')
.replace(/^image-template-/, '')
.replace(/^od-/, 'od-');
const BLOG_TOPIC_TITLES: Record<string, Partial<Record<Exclude<LandingLocaleCode, 'en'>, string>>> = {
'31-skills-72-systems-how-the-library-works': {
zh: '31 个 Skill 与 72 个系统的资料库运作方式',
'zh-tw': '31 個 Skill 與 72 個系統的資料庫運作方式',
ja: '31個のSkillと72個のシステムのライブラリ構造',
ko: '31개 Skill과 72개 시스템 라이브러리의 작동 방식',
de: 'wie die Bibliothek mit 31 Skills und 72 Systemen funktioniert',
fr: 'le fonctionnement de la bibliothèque de 31 skills et 72 systèmes',
ru: 'как работает библиотека из 31 навыка и 72 систем',
es: 'cómo funciona la biblioteca de 31 skills y 72 sistemas',
'pt-br': 'como funciona a biblioteca de 31 skills e 72 sistemas',
it: 'come funziona la libreria con 31 skill e 72 sistemi',
vi: 'cách vận hành thư viện 31 skill và 72 hệ thống',
pl: 'jak działa biblioteka 31 skill i 72 systemów',
id: 'cara kerja pustaka 31 skill dan 72 sistem',
nl: 'hoe de bibliotheek met 31 skills en 72 systemen werkt',
ar: 'طريقة عمل مكتبة تضم 31 مهارة و72 نظاما',
tr: '31 skill ve 72 sistemden oluşan kitaplığın çalışma biçimi',
uk: 'як працює бібліотека з 31 навички та 72 систем',
},
'byok-design-workflow-claude-codex-qwen': {
zh: '面向 Claude、Codex 与 Qwen 的 BYOK 设计工作流',
'zh-tw': '面向 Claude、Codex 與 Qwen 的 BYOK 設計工作流',
ja: 'Claude、Codex、Qwen向けBYOKデザインワークフロー',
ko: 'Claude, Codex, Qwen을 위한 BYOK 디자인 워크플로',
de: 'BYOK-Designworkflow für Claude, Codex und Qwen',
fr: 'workflow de design BYOK pour Claude, Codex et Qwen',
ru: 'BYOK-дизайн-процесс для Claude, Codex и Qwen',
es: 'flujo de diseño BYOK para Claude, Codex y Qwen',
'pt-br': 'fluxo de design BYOK para Claude, Codex e Qwen',
it: 'workflow di design BYOK per Claude, Codex e Qwen',
vi: 'quy trình thiết kế BYOK cho Claude, Codex và Qwen',
pl: 'workflow projektowy BYOK dla Claude, Codex i Qwen',
id: 'alur desain BYOK untuk Claude, Codex, dan Qwen',
nl: 'BYOK-designworkflow voor Claude, Codex en Qwen',
ar: 'سير عمل تصميم BYOK مع Claude وCodex وQwen',
tr: 'Claude, Codex ve Qwen için BYOK tasarım akışı',
uk: 'BYOK дизайн-процес для Claude, Codex і Qwen',
},
'byok-reality-check-5-things-that-break': {
zh: 'BYOK 现实检查5 个容易断裂的环节',
'zh-tw': 'BYOK 現實檢查5 個容易斷裂的環節',
ja: 'BYOKの現実チェック: 壊れやすい5つの点',
ko: 'BYOK 현실 점검: 깨지기 쉬운 5가지 지점',
de: 'BYOK-Realitätscheck: fünf Dinge, die brechen',
fr: 'réalité BYOK : cinq points qui cassent',
ru: 'проверка BYOK на практике: пять слабых мест',
es: 'revisión realista de BYOK: cinco puntos que fallan',
'pt-br': 'checagem realista do BYOK: cinco pontos que quebram',
it: 'reality check BYOK: cinque punti che si rompono',
vi: 'kiểm tra thực tế BYOK: 5 điểm dễ hỏng',
pl: 'sprawdzenie BYOK w praktyce: pięć miejsc awarii',
id: 'cek realitas BYOK: lima hal yang mudah rusak',
nl: 'BYOK-realiteitscheck: vijf dingen die breken',
ar: 'اختبار واقعي ل BYOK: خمسة مواضع تتعطل',
tr: 'BYOK gerçeklik kontrolü: bozulan beş nokta',
uk: 'реалістична перевірка BYOK: пʼять місць, які ламаються',
},
'layout-layer-canvas-used-to-hide': {
zh: '过去被画布隐藏的布局层',
'zh-tw': '過去被畫布隱藏的版面層',
ja: 'キャンバスが隠していたレイアウト層',
ko: '캔버스가 숨겨 왔던 레이아웃 계층',
de: 'die Layoutschicht, die Canvas früher verborgen hat',
fr: 'la couche de mise en page que le canvas cachait',
ru: 'слой макета, который раньше скрывал canvas',
es: 'la capa de layout que antes ocultaba el canvas',
'pt-br': 'a camada de layout que o canvas escondia',
it: 'il livello di layout che il canvas nascondeva',
vi: 'lớp bố cục từng bị canvas che khuất',
pl: 'warstwa layoutu, którą dawniej ukrywał canvas',
id: 'lapisan layout yang dulu disembunyikan kanvas',
nl: 'de layoutlaag die canvas vroeger verborg',
ar: 'طبقة التخطيط التي كان canvas يخفيها',
tr: 'canvasın eskiden sakladığı yerleşim katmanı',
uk: 'шар макета, який раніше приховував canvas',
},
'open-source-alternative-to-claude-design': {
zh: 'Claude Design 的开源替代方案',
'zh-tw': 'Claude Design 的開源替代方案',
ja: 'Claude Designのオープンソース代替',
ko: 'Claude Design의 오픈소스 대안',
de: 'Open-Source-Alternative zu Claude Design',
fr: 'alternative open source à Claude Design',
ru: 'open-source альтернатива Claude Design',
es: 'alternativa open source a Claude Design',
'pt-br': 'alternativa open source ao Claude Design',
it: 'alternativa open source a Claude Design',
vi: 'giải pháp mã nguồn mở thay cho Claude Design',
pl: 'open source alternatywa dla Claude Design',
id: 'alternatif open source untuk Claude Design',
nl: 'open-source alternatief voor Claude Design',
ar: 'بديل مفتوح المصدر ل Claude Design',
tr: 'Claude Design için açık kaynak alternatif',
uk: 'open-source альтернатива Claude Design',
},
'port-figma-workflow-open-design-plugin': {
zh: '把 Figma 工作流迁移成 Open Design 插件',
'zh-tw': '把 Figma 工作流遷移成 Open Design 外掛',
ja: 'FigmaワークフローをOpen Designプラグインへ移植する',
ko: 'Figma 워크플로를 Open Design 플러그인으로 옮기기',
de: 'Figma-Workflows als Open-Design-Plugin portieren',
fr: 'porter un workflow Figma en plugin Open Design',
ru: 'перенос Figma-процесса в плагин Open Design',
es: 'llevar un flujo de Figma a un plugin de Open Design',
'pt-br': 'migrar um fluxo do Figma para um plugin Open Design',
it: 'portare un workflow Figma in un plugin Open Design',
vi: 'chuyển quy trình Figma thành plugin Open Design',
pl: 'przenoszenie workflow Figma do pluginu Open Design',
id: 'memindahkan alur Figma menjadi plugin Open Design',
nl: 'een Figma-workflow omzetten naar een Open Design-plugin',
ar: 'نقل سير عمل Figma إلى إضافة Open Design',
tr: 'Figma akışını Open Design eklentisine taşıma',
uk: 'перенесення Figma-процесу в плагін Open Design',
},
'why-we-built-open-design-as-a-skill-layer': {
zh: '为什么把 Open Design 做成 Skill 层',
'zh-tw': '為什麼把 Open Design 做成 Skill 層',
ja: 'Open DesignをSkillレイヤーとして作った理由',
ko: 'Open Design을 Skill 레이어로 만든 이유',
de: 'warum Open Design als Skill-Schicht gebaut wurde',
fr: 'pourquoi Open Design est une couche de skills',
ru: 'почему Open Design построен как слой навыков',
es: 'por qué Open Design se construyó como capa de skills',
'pt-br': 'por que o Open Design foi criado como camada de skills',
it: 'perché Open Design è stato costruito come livello di skill',
vi: 'vì sao Open Design được xây như một lớp skill',
pl: 'dlaczego Open Design powstał jako warstwa skill',
id: 'mengapa Open Design dibangun sebagai lapisan skill',
nl: 'waarom Open Design als skill-laag is gebouwd',
ar: 'لماذا بنينا Open Design كطبقة مهارات',
tr: 'Open Design neden bir skill katmanı olarak kuruldu',
uk: 'чому Open Design створено як шар навичок',
},
};
const localizedBlogTopic = (id: string, locale: LandingLocaleCode) => {
const compact = compactId(id);
if (locale === DEFAULT_LOCALE) return compact.replace(/-/g, ' ');
return BLOG_TOPIC_TITLES[compact]?.[locale] ?? compact.replace(/-/g, ' ');
};
export function explicitLocalizedString(
value: LocalizedStringValue,
locale: LandingLocaleCode,
): string | undefined {
if (typeof value === 'string') {
return locale === DEFAULT_LOCALE && value.trim() ? value.trim() : undefined;
}
if (!value || typeof value !== 'object') return undefined;
const localeDef = getLocaleDefinition(locale);
const candidates = [
locale,
localeDef.htmlLang,
localeDef.htmlLang.toLowerCase(),
localeDef.htmlLang.replace('-', '_'),
locale === 'zh' ? 'zh-CN' : undefined,
locale === 'zh-tw' ? 'zh-TW' : undefined,
locale === 'pt-br' ? 'pt-BR' : undefined,
].filter((item): item is string => Boolean(item));
for (const key of candidates) {
const text = value[key];
if (typeof text === 'string' && text.trim()) {
return text.trim();
}
}
return undefined;
}
export function localizeTaxonomyValue(
value: string | undefined,
locale: LandingLocaleCode,
): string | undefined {
if (!value) return undefined;
const key = normalizeTerm(value);
// Plugins-i18n's 23-key `subcategory` map covers scene-level slugs like
// `business-dashboards` and `social-short-form` — values that originate
// from `od.scenario` on bundled plugins and never appear in TAXONOMY_TERMS
// or CATEGORY_LABELS. Consulting it here gives English a friendly label
// ("Dashboards") instead of the raw kebab key, and lets every chip
// consumer pick up scene-level translations on non-English locales.
const subcategoryLabel = getPluginsCopy(locale).subcategory[key];
if (locale === DEFAULT_LOCALE) return subcategoryLabel ?? value;
// Return undefined when no real translation is found, so chip-rail
// consumers can drop the chip entirely rather than render a noisy
// "Category" / "分類" placeholder for every taxonomy slug we have not
// localized yet (`design-system`, `planning`, `code-migration`, etc.).
// Callers that genuinely want the unknownTag placeholder should use
// `localizeContentTag` instead, which keeps the explicit fallback.
return (
TAXONOMY_TERMS[key]?.[locale] ??
CATEGORY_LABELS[key]?.[locale] ??
subcategoryLabel
);
}
export function localizeContentTag(
value: string | undefined,
locale: LandingLocaleCode,
): string | undefined {
if (!value) return undefined;
if (locale === DEFAULT_LOCALE) return value;
return localizeTaxonomyValue(value, locale) ?? copyFor(locale)?.unknownTag;
}
/*
* Mixed-language guard used by every `localizeXxxText` helper below.
*
* The legacy fallback templates for craft / template / system / plugin /
* blog are Chinese / Japanese / Korean sentences that splice an English
* `name` into themselves: ``${name}工艺规则`` produces "Editorial
* typography hierarchy 工艺规则" when the source material is still in
* English. That mid-sentence script switch reads as broken on
* `/zh/...`, `/zh-tw/...`, `/ja/...`, `/ko/...` even when chrome around
* it is fully localized.
*
* Until the source-of-truth (SKILL.md frontmatter, design-system /
* craft markdown) ships per-locale `name` fields, the cleaner UX is to
* render the section in English on a CJK locale: chrome stays in the
* visitor's language, the body reads like an untranslated source
* snippet (which is what it actually is), and the awkward script
* straddling goes away.
*/
const CJK_CHAR_RE = /[぀-ゟ゠-ヿㇰ-ㇿ가-힯一-鿿豈-﫿]/;
const CJK_LOCALES = new Set<LandingLocaleCode>(['zh', 'zh-tw', 'ja', 'ko']);
function nameNeedsEnglishFallback(name: string, locale: LandingLocaleCode): boolean {
if (!CJK_LOCALES.has(locale)) return false;
return !CJK_CHAR_RE.test(name);
}
export function localizeSkillDescription(args: {
name: string;
mode?: string;
scenario?: string;
category?: string;
locale: LandingLocaleCode;
fallback: string;
}): string {
const copy = copyFor(args.locale);
if (!copy) return args.fallback;
if (nameNeedsEnglishFallback(args.name, args.locale)) return args.fallback;
const labels = [args.mode, args.scenario, args.category]
.map((value) => localizeTaxonomyValue(value, args.locale))
.filter((value): value is string => Boolean(value));
return copy.skillDescription(args.name, Array.from(new Set(labels)));
}
export function localizeSystemText(args: {
name: string;
category: string;
paletteCount: number;
locale: LandingLocaleCode;
fallbackTagline: string;
fallbackAtmosphere: string;
}): { category: string; tagline: string; atmosphere: string } {
const copy = copyFor(args.locale);
if (!copy) {
return {
category: args.category,
tagline: args.fallbackTagline,
atmosphere: args.fallbackAtmosphere,
};
}
if (nameNeedsEnglishFallback(args.name, args.locale)) {
return {
category: args.category,
tagline: args.fallbackTagline,
atmosphere: args.fallbackAtmosphere,
};
}
const category = localizeTaxonomyValue(args.category, args.locale) ?? copy.systemNoun;
return {
category,
tagline: copy.systemTagline(args.name, category),
atmosphere: copy.systemAtmosphere(args.name, category, args.paletteCount),
};
}
export function localizeCraftText(args: {
slug: string;
name: string;
summary: string;
locale: LandingLocaleCode;
}): { name: string; summary: string } {
const copy = copyFor(args.locale);
if (!copy) return { name: args.name, summary: args.summary };
const baseName = CRAFT_LABELS[args.slug]?.[args.locale] ?? args.name;
if (nameNeedsEnglishFallback(baseName, args.locale)) {
return { name: args.name, summary: args.summary };
}
return {
name: copy.craftName(baseName),
summary: copy.craftSummary(baseName),
};
}
export function localizeTemplateText(args: {
name: string;
summary: string;
locale: LandingLocaleCode;
}): { name: string; summary: string } {
const copy = copyFor(args.locale);
if (!copy) return { name: args.name, summary: args.summary };
if (nameNeedsEnglishFallback(args.name, args.locale)) {
return { name: args.name, summary: args.summary };
}
return {
name: copy.templateName(args.name),
summary: copy.templateSummary(args.name),
};
}
export function localizePluginText(args: {
id: string;
title: string;
description: string;
locale: LandingLocaleCode;
mode?: string;
taskKind?: string;
surface?: string;
visualKind?: string;
labels?: string[];
}): { title: string; description: string; exampleQuery: string | undefined } {
const copy = copyFor(args.locale);
if (!copy) {
return {
title: args.title,
description: args.description,
exampleQuery: undefined,
};
}
if (nameNeedsEnglishFallback(args.title, args.locale)) {
return {
title: args.title,
description: args.description,
exampleQuery: undefined,
};
}
const kind =
localizeTaxonomyValue(args.mode ?? args.surface ?? args.visualKind, args.locale) ??
copy.pluginNoun;
const labels = (args.labels ?? [])
.map((value) => localizeTaxonomyValue(value, args.locale))
.filter((value): value is string => Boolean(value));
return {
title: copy.pluginTitle(kind, compactId(args.id)),
description: copy.pluginDescription(kind, Array.from(new Set(labels)).slice(0, 4)),
exampleQuery: copy.pluginExample(kind),
};
}
export function localizeBlogPostText(args: {
id: string;
title: string;
summary: string;
category: string;
locale: LandingLocaleCode;
}): { title: string; summary: string; category: string; bodyHtml: string | undefined } {
const copy = copyFor(args.locale);
if (!copy) {
return {
title: args.title,
summary: args.summary,
category: args.category,
bodyHtml: undefined,
};
}
// Blog posts go through `localizedBlogTopic`, which has its own per-id
// translation table; if the topic isn't there the helper returns the raw
// English title — wrapping that in a Chinese sentence template ("Open
// Design 指南BYOK reality check") would mix scripts the same way craft
// / template / system do. Same guard applies.
const topic = localizedBlogTopic(args.id, args.locale);
if (nameNeedsEnglishFallback(topic, args.locale)) {
return {
title: args.title,
summary: args.summary,
category: args.category,
bodyHtml: undefined,
};
}
const title = copy.blogTitle(topic);
const summary = copy.blogSummary(topic);
return {
title,
summary,
category: localizeTaxonomyValue(args.category, args.locale) ?? copy.blogNoun,
bodyHtml: copy.blogBody(topic, summary),
};
}