Skip to content

Commit b78c555

Browse files
committed
fix(FR-2765): docs site follow-ups — Lablup transliteration, 가/A icon, sort order (#7133)
Resolves #__pending(FR-2765) — Jira: https://lablup.atlassian.net/browse/FR-2765 > Note: GitHub clone of FR-2765 is in flight; this PR description will be amended once the cloned issue number is available. ## Summary Four small follow-ups from the Frontend Teams thread (2026-04-28). All scoped to the docs site (`packages/backend.ai-docs-toolkit/` + `packages/backend.ai-webui-docs/`); ship as one PR. ### 1. Korean homeIntro: "Lablup" → "래블업" Jinho Heo's request — use the standard 래블업 transliteration in the Korean home-page intro paragraph. Pure label change in `WEBSITE_LABELS.ko.homeIntro`; other languages unchanged. ### 2. Language switcher icon: 가 / A Replaces the Lucide `languages` glyph (which read as a generic squiggle and didn't telegraph "Korean ↔ Latin" to the user). The new icon is a 가 / A pair (one Hangul syllable + one Latin uppercase letter), matching the Material Symbols `language_korean_latin` idea. The glyph outlines are baked in as inline `<path>` data with `fill: currentColor`, generated offline from Noto Sans CJK KR Bold via `fontTools.pens.svgPathPen.SVGPathPen` (U+AC00 + U+0041, font-size 13, baselines (1, 12) and (11, 23) inside a 24×24 viewBox). This avoids depending on the visitor having a CJK font installed and keeps the icon air-gap-safe. The const carries reproducibility notes so future re-tunes stay deterministic. ### 3. Version selector: `next` first Reorder `versions:` in `docs-toolkit.config.yaml` so the dev/preview `next` channel surfaces at the top of the version `<select>`. Stable-channel readers still see the `(latest)` marker that the switcher attaches to whichever entry carries `latest: true`, so the cue is preserved. ### 4. Language switcher: `ko` first Reorder `languages:` in `book.config.yaml` so 한국어 surfaces at the top of the language `<select>`. Build iteration order shifts but is output-only — no consumer relies on it. ## Verification - `pnpm --filter backend.ai-docs-toolkit build` — clean. - `pnpm --filter backend.ai-docs-toolkit test` — 150/150 pass. - `bash scripts/verify.sh` — Relay / Lint / Format / TypeScript ✅. - Local site rebuild + inspection (`pnpm --filter backend.ai-webui-docs serve:web`): - `next/ko/index.html` — homeIntro shows "래블업의 분산..." - `next/ko/quickstart.html` — language `<select>` order: 한국어 → English → 日本語 → ภาษาไทย; version `<select>` order: next (selected) → 26.4 (latest) - Topbar language icon: single `<path>` (no `<text>`, no font dependency) ## Test plan - [ ] Reviewer can verify on `webui-docs-next.lablup.ai` after merge + Amplify deploy. - [ ] Confirm 가/A glyph renders with `currentColor` in dark mode. - [ ] Confirm version `<select>` shows `next` first on every chapter page. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 13f209a commit b78c555

5 files changed

Lines changed: 140 additions & 30 deletions

File tree

packages/backend.ai-docs-toolkit/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ export const WEBSITE_LABELS: Record<string, Record<string, string>> = {
441441
downloadPdfThisVersion: "PDF 다운로드 (이 버전)",
442442
homeWelcome: "Backend.AI WebUI 사용자 매뉴얼에 오신 것을 환영합니다",
443443
homeIntro:
444-
"Backend.AI WebUI는 Lablup의 분산 AI/ML 컴퓨팅 플랫폼인 Backend.AI를 위한 공식 브라우저 기반 관리 콘솔입니다. 이 매뉴얼은 첫 연산 세션 생성부터 멀티 테넌트 운영까지, 모든 화면과 워크플로우를 단계별로 안내합니다.",
444+
"Backend.AI WebUI는 래블업의 분산 AI/ML 컴퓨팅 플랫폼인 Backend.AI를 위한 공식 브라우저 기반 관리 콘솔입니다. 이 매뉴얼은 첫 연산 세션 생성부터 멀티 테넌트 운영까지, 모든 화면과 워크플로우를 단계별로 안내합니다.",
445445
homeAboutWebUI: "Backend.AI WebUI 소개",
446446
homeAboutWebUIBody:
447447
"Backend.AI 클러스터에 접속해 CPU·GPU 위에서 대화형 또는 배치 세션을 실행하고, 팀과 스토리지 폴더를 공유하며, 학습된 모델을 추론 엔드포인트로 서빙하세요. 모든 작업을 브라우저에서 수행할 수 있습니다.",

packages/backend.ai-docs-toolkit/src/website-builder.test.ts

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,59 @@ describe("buildWebPage — FR-2726 surfaces", () => {
227227
);
228228
});
229229

230+
it("renders language <option> entries in the order supplied by peers (FR-2765)", () => {
231+
// Surface 한국어 at the top of the language dropdown by feeding peers
232+
// in [ko, en, ja, th] order. The renderer must respect that order
233+
// verbatim — without this guarantee, reordering `languages:` in
234+
// book.config.yaml (the source of truth for peer order) would not
235+
// change the user-visible <select> order.
236+
const ctx = makeContext();
237+
ctx.metadata = {
238+
...ctx.metadata,
239+
lang: "ko",
240+
availableLanguages: ["ko", "en", "ja", "th"],
241+
};
242+
ctx.peers = [
243+
{
244+
lang: "ko",
245+
label: "한국어",
246+
href: "../ko/dashboard.html",
247+
available: true,
248+
},
249+
{
250+
lang: "en",
251+
label: "English",
252+
href: "../en/dashboard.html",
253+
available: true,
254+
},
255+
{
256+
lang: "ja",
257+
label: "日本語",
258+
href: "../ja/dashboard.html",
259+
available: true,
260+
},
261+
{
262+
lang: "th",
263+
label: "ภาษาไทย",
264+
href: "../th/dashboard.html",
265+
available: true,
266+
},
267+
];
268+
const html = buildWebPage(ctx);
269+
270+
// Anchor on the topbar's lang-switcher <select> (avoid colliding with
271+
// any other <select> rendered downstream, e.g. the version switcher).
272+
const selectMatch = html.match(
273+
/<select[^>]*class="lang-switcher__select"[\s\S]*?<\/select>/,
274+
);
275+
assert.ok(selectMatch, "lang-switcher__select not found");
276+
const selectHtml = selectMatch[0];
277+
const optionLabels = Array.from(
278+
selectHtml.matchAll(/<option[^>]*>([^<]+)<\/option>/g),
279+
).map((m) => m[1]);
280+
assert.deepEqual(optionLabels, ["한국어", "English", "日本語", "ภาษาไทย"]);
281+
});
282+
230283
it("emits the GitHub icon link only when website.repoUrl is set", () => {
231284
const html = buildWebPage(makeContext());
232285
assert.match(html, /aria-label="GitHub"/);
@@ -577,13 +630,43 @@ describe("buildWebPage — FR-2733 sidebar version block placement", () => {
577630
assert.doesNotMatch(html, /class="doc-sidebar-version"/);
578631
assert.doesNotMatch(html, /id="version-switcher"/);
579632
});
633+
634+
it("renders version <option> entries in the order supplied by allLabels (FR-2765)", () => {
635+
// Surface `next` at the top of the version dropdown by feeding
636+
// allLabels in [next, 26.4] order. The renderer must respect that
637+
// order verbatim — without this guarantee, reordering `versions:`
638+
// in docs-toolkit.config.yaml (the source of truth for switcher
639+
// order) would not change the user-visible <select> order.
640+
const ctx = makeVersionedContext();
641+
ctx.versionContext = {
642+
...ctx.versionContext!,
643+
current: "next",
644+
allLabels: ["next", "26.4"],
645+
latest: "26.4",
646+
};
647+
const html = buildWebPage(ctx);
648+
649+
const selectMatch = html.match(
650+
/<select[^>]*id="version-switcher"[\s\S]*?<\/select>/,
651+
);
652+
assert.ok(selectMatch, "version-switcher <select> not found");
653+
const selectHtml = selectMatch[0];
654+
const optionLabels = Array.from(
655+
selectHtml.matchAll(/<option[^>]*>([^<]+)<\/option>/g),
656+
).map((m) => m[1]);
657+
// First option is `next` (no "(latest)" marker — that attaches to
658+
// whichever entry is flagged latest); second is `26.4 (latest)`.
659+
assert.deepEqual(optionLabels, ["next", "26.4 (latest)"]);
660+
});
580661
});
581662

582663
describe("buildWebPage / buildHomePage — FR-2758 sidebar 'Introduction' entry", () => {
583664
it("renders an Introduction anchor at the top of the sidebar linking to ./index.html", () => {
584665
const html = buildWebPage(makeContext());
585666

586-
const sidebarMatch = html.match(/<aside class="doc-sidebar">[\s\S]*?<\/aside>/);
667+
const sidebarMatch = html.match(
668+
/<aside class="doc-sidebar">[\s\S]*?<\/aside>/,
669+
);
587670
assert.ok(sidebarMatch, ".doc-sidebar aside not found");
588671
const sidebarHtml = sidebarMatch[0];
589672

@@ -647,10 +730,7 @@ describe("buildWebPage / buildHomePage — FR-2758 sidebar 'Introduction' entry"
647730
const koCtx = makeContext();
648731
koCtx.metadata = { ...koCtx.metadata, lang: "ko" };
649732
const koHtml = buildWebPage(koCtx);
650-
assert.match(
651-
koHtml,
652-
/<span class="doc-sidebar-intro__label"><\/span>/,
653-
);
733+
assert.match(koHtml, /<span class="doc-sidebar-intro__label"><\/span>/);
654734

655735
const jaCtx = makeContext();
656736
jaCtx.metadata = { ...jaCtx.metadata, lang: "ja" };
@@ -663,10 +743,7 @@ describe("buildWebPage / buildHomePage — FR-2758 sidebar 'Introduction' entry"
663743
const thCtx = makeContext();
664744
thCtx.metadata = { ...thCtx.metadata, lang: "th" };
665745
const thHtml = buildWebPage(thCtx);
666-
assert.match(
667-
thHtml,
668-
/<span class="doc-sidebar-intro__label"><\/span>/,
669-
);
746+
assert.match(thHtml, /<span class="doc-sidebar-intro__label"><\/span>/);
670747
});
671748
});
672749

packages/backend.ai-docs-toolkit/src/website-builder.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -459,12 +459,27 @@ function sidebarCategoryIconSvg(group: NavGroup): string {
459459
}
460460

461461
/**
462-
* Lucide `languages` icon — used to signal the purpose of the topbar
463-
* language switcher (FR-2737). Inline SVG, currentColor stroke so it
464-
* inherits the surrounding text color in both light and dark themes.
465-
* Source: https://lucide.dev/icons/languages
462+
* Language-switcher icon (FR-2765). Replaces the previous Lucide
463+
* `languages` glyph (which read as a generic "two-character set" for
464+
* non-CJK readers and as a generic squiggle for everyone else) with a
465+
* 가 / A pair, matching the Material Symbols `language_korean_latin`
466+
* idea: one Hangul syllable + one Latin uppercase letter so the
467+
* control's purpose is unambiguous to readers who recognize either
468+
* script.
469+
*
470+
* The glyph outlines are baked in as a single `<path>` with `fill:
471+
* currentColor`, generated offline from Noto Sans CJK KR Bold via
472+
* fontTools. This avoids depending on the visitor's system having a
473+
* CJK font available — without it, an SVG `<text>` "가" would fall
474+
* back to a tofu box on stripped-down environments. Air-gap safe.
475+
*
476+
* Path-generation reproducibility: the `d` attribute below was emitted
477+
* by `fontTools.pens.svgPathPen.SVGPathPen` from glyphs U+AC00 (가) and
478+
* U+0041 (A) at font-size 13, baselines (1, 12) and (11, 23), inside
479+
* a 24×24 viewBox. Re-run the conversion if the visual scale or
480+
* placement ever changes; do not hand-edit the path.
466481
*/
467-
const LUCIDE_LANGUAGES_ICON_SVG = `<svg class="lang-switcher__icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 8 6 6"/><path d="m4 14 6-6 2-3"/><path d="M2 5h12"/><path d="M7 2h1"/><path d="m22 22-5-10-5 10"/><path d="M14 18h6"/></svg>`;
482+
const LANGUAGE_SWITCHER_ICON_SVG = `<svg class="lang-switcher__icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M9.22 1.09V13.13H10.96V7.12H12.63V5.71H10.96V1.09ZM2.07 2.34V3.73H5.99C5.69 6.41 4.2 8.31 1.4 9.75L2.38 11.06C6.38 9.05 7.76 5.96 7.76 2.34Z M10.95 23H12.9L13.57 20.53H16.68L17.36 23H19.38L16.3 13.37H14.03ZM13.99 19.04 14.28 17.98C14.56 16.98 14.84 15.89 15.09 14.84H15.15C15.43 15.86 15.69 16.98 15.99 17.98L16.28 19.04Z"/></svg>`;
468483

469484
// Canonical Lucide `rocket` icon. Swapped in (FR-2737) for the
470485
// hand-drawn placeholder, whose silhouette read as a horizontal blob
@@ -1372,15 +1387,18 @@ function buildBaiTopbar(
13721387
// localization passes through to search.js.
13731388
void noResultsAttr;
13741389

1375-
// Lang switcher (FR-2737 — icon-only variant).
1390+
// Lang switcher (FR-2737 — icon-only variant; icon updated in FR-2765).
13761391
//
1377-
// The visible affordance is a single Lucide `languages` icon. A
1378-
// transparent native `<select>` is layered on top of the icon, so
1379-
// clicking the icon opens the OS-native option list — every browser
1380-
// and platform (incl. mobile) renders this dropdown using its own
1381-
// accessible UI. We don't reinvent the menu in JS: keyboard support,
1382-
// screen-reader announcements, and touch-friendly option pickers all
1383-
// come "for free" from the native control.
1392+
// The visible affordance is a single 가 / A pair (Hangul + Latin)
1393+
// baked in as inline SVG `<path>` data — see
1394+
// `LANGUAGE_SWITCHER_ICON_SVG` above for the source-font /
1395+
// glyph-extraction details. A transparent native `<select>` is
1396+
// layered on top of the icon, so clicking the icon opens the OS-
1397+
// native option list — every browser and platform (incl. mobile)
1398+
// renders this dropdown using its own accessible UI. We don't
1399+
// reinvent the menu in JS: keyboard support, screen-reader
1400+
// announcements, and touch-friendly option pickers all come "for
1401+
// free" from the native control.
13841402
//
13851403
// Pages where the chapter is missing in a peer language still get an
13861404
// option, but its value is that language's `index.html` so the click
@@ -1408,7 +1426,7 @@ function buildBaiTopbar(
14081426
// label association.
14091427
const langSwitcherHtml = `<label class="lang-switcher" aria-label="Language switcher">
14101428
<select class="lang-switcher__select" aria-label="Language switcher" onchange="if(this.value)window.location.assign(this.value)">${langOptions}</select>
1411-
${LUCIDE_LANGUAGES_ICON_SVG}
1429+
${LANGUAGE_SWITCHER_ICON_SVG}
14121430
</label>`;
14131431

14141432
// FR-2733: the version selector used to render here in the topbar

packages/backend.ai-webui-docs/docs-toolkit.config.yaml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,33 @@ pdfMetadata:
3939
# assets are linked from the per-language landing page (FR-2732).
4040
#
4141
# Release procedure (when cutting a new minor X.Y):
42-
# 1. Insert a new entry ABOVE the `next` entry with
42+
# 1. Insert a new entry IMMEDIATELY BELOW the `next` entry (above the
43+
# previous-latest minor) with
4344
# label: "X.Y"
4445
# source: { kind: archive-branch, ref: docs-archive/X.Y }
4546
# pdfTag: "vX.Y.Z" # highest patch in the minor
4647
# 2. Move `latest: true` onto the new entry; remove it from the prior one.
47-
# 3. Leave the `next` entry last; it never carries `latest: true`.
48+
# 3. Keep the `next` entry at the TOP of the list (FR-2765 — surfaces
49+
# `next` at the top of the version <select>); it never carries
50+
# `latest: true`.
4851
# See the docs release runbook for the full procedure.
4952
versions:
53+
# FR-2765: `next` is intentionally listed first so it appears at the
54+
# top of the version `<select>` dropdown. Readers visiting `next` see
55+
# it as the active option without scrolling, and stable-channel
56+
# readers still see the "(latest)" marker that the switcher renders
57+
# next to whichever entry has `latest: true`. Build iteration follows
58+
# this list order, but build order is output-only — no consumer of
59+
# the build relies on it.
60+
- label: "next"
61+
source:
62+
kind: workspace
5063
- label: "26.4"
5164
source:
5265
kind: archive-branch
5366
ref: docs-archive/26.4
5467
latest: true
5568
pdfTag: "v26.4.7"
56-
- label: "next"
57-
source:
58-
kind: workspace
5969

6070
# Product name for log messages
6171
productName: "Backend.AI WebUI Docs"

packages/backend.ai-webui-docs/src/book.config.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ title: |
44
User Guide
55
description: Multilingual user manual by Lablup Inc.
66
type: document
7+
# FR-2765: ko first so the language switcher surfaces 한국어 at the top
8+
# of the dropdown. The build pipeline iterates this list to enumerate
9+
# per-language pages, but iteration order is output-only — no consumer
10+
# of the build relies on it. Remaining order (en, ja, th) preserved
11+
# for downstream stability.
712
languages:
13+
- ko
814
- en
915
- ja
10-
- ko
1116
- th
1217
# F3 — Information architecture
1318
# ----------------------------------------------------------------------------

0 commit comments

Comments
 (0)