mirror of
https://github.com/nexu-io/open-design.git
synced 2026-07-03 12:27:55 +08:00
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.
This commit is contained in:
@@ -711,6 +711,32 @@ export function localizeContentTag(
|
||||
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;
|
||||
@@ -721,6 +747,7 @@ export function localizeSkillDescription(args: {
|
||||
}): 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));
|
||||
@@ -743,6 +770,13 @@ export function localizeSystemText(args: {
|
||||
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,
|
||||
@@ -760,6 +794,9 @@ export function localizeCraftText(args: {
|
||||
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),
|
||||
@@ -773,6 +810,9 @@ export function localizeTemplateText(args: {
|
||||
}): { 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),
|
||||
@@ -798,6 +838,13 @@ export function localizePluginText(args: {
|
||||
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;
|
||||
@@ -827,7 +874,20 @@ export function localizeBlogPostText(args: {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user