-
Notifications
You must be signed in to change notification settings - Fork 0
feat(wiki-compose): add Japanese UI and localized LLM output #985
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { | ||
| composeConflictRationale, | ||
| composeContentLocaleInstruction, | ||
| normalizeComposeContentLocale, | ||
| resolveComposeContentLocale, | ||
| stripContentLocaleFromGraphInput, | ||
| structureDialogueFallbackOutline, | ||
| } from "../../../agents/core/composeLocale.js"; | ||
|
|
||
| describe("composeLocale", () => { | ||
| it("normalizes supported locales only", () => { | ||
| expect(normalizeComposeContentLocale("ja")).toBe("ja"); | ||
| expect(normalizeComposeContentLocale("en")).toBe("en"); | ||
| expect(normalizeComposeContentLocale("fr")).toBeNull(); | ||
| }); | ||
|
|
||
| it("resolves from input then Accept-Language", () => { | ||
| expect(resolveComposeContentLocale({ contentLocale: "en" }, "ja-JP")).toBe("en"); | ||
| expect(resolveComposeContentLocale({}, "ja-JP,en;q=0.8")).toBe("ja"); | ||
| expect(resolveComposeContentLocale({}, "en-US,en;q=0.9")).toBe("en"); | ||
| expect(resolveComposeContentLocale({}, null, "ja")).toBe("ja"); | ||
| }); | ||
|
|
||
| it("strips contentLocale from graph input", () => { | ||
| expect(stripContentLocaleFromGraphInput({ contentLocale: "ja", chatSeed: { x: 1 } })).toEqual({ | ||
| chatSeed: { x: 1 }, | ||
| }); | ||
| }); | ||
|
|
||
| it("includes Japanese in locale instruction when ja", () => { | ||
| expect(composeContentLocaleInstruction("ja")).toMatch(/Japanese/); | ||
| expect(composeContentLocaleInstruction("en")).toMatch(/English/); | ||
| }); | ||
|
|
||
| it("provides localized conflict rationale and fallback outline", () => { | ||
| expect(composeConflictRationale("ja")).toMatch(/却下/); | ||
| expect(structureDialogueFallbackOutline("ja")[0]?.heading).toBe("概要"); | ||
| expect(structureDialogueFallbackOutline("en")[0]?.heading).toBe("Overview"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Content locale for Wiki Compose LLM outputs (#950). | ||||||||||||||||||||||
| * Wiki Compose の生成言語(ユーザー向けテキスト)を表す。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export type ComposeContentLocale = "ja" | "en"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Normalize an arbitrary value to a supported compose content locale. | ||||||||||||||||||||||
| * 任意の入力をサポートされる compose 用ロケールに正規化する。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export function normalizeComposeContentLocale(raw: unknown): ComposeContentLocale | null { | ||||||||||||||||||||||
| if (raw === "ja" || raw === "en") return raw; | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Resolve locale from graph run input (`contentLocale`) with a server default. | ||||||||||||||||||||||
| * graph の run input(`contentLocale`)からロケールを解決する。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export function resolveComposeContentLocale( | ||||||||||||||||||||||
| input: unknown, | ||||||||||||||||||||||
| acceptLanguage: string | undefined | null, | ||||||||||||||||||||||
| fallback: ComposeContentLocale = "ja", | ||||||||||||||||||||||
| ): ComposeContentLocale { | ||||||||||||||||||||||
| if (input && typeof input === "object" && "contentLocale" in input) { | ||||||||||||||||||||||
| const fromInput = normalizeComposeContentLocale( | ||||||||||||||||||||||
| (input as { contentLocale?: unknown }).contentLocale, | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (fromInput) return fromInput; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (acceptLanguage) { | ||||||||||||||||||||||
| const primary = acceptLanguage.split(",")[0]?.trim().toLowerCase() ?? ""; | ||||||||||||||||||||||
| if (primary.startsWith("ja")) return "ja"; | ||||||||||||||||||||||
| if (primary.startsWith("en")) return "en"; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||
| return fallback; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Strip `contentLocale` before passing input to LangGraph (not a state channel). | ||||||||||||||||||||||
| * LangGraph に渡す前に `contentLocale` を除去する(state チャネルではない)。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export function stripContentLocaleFromGraphInput(raw: unknown): unknown { | ||||||||||||||||||||||
| if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw ?? {}; | ||||||||||||||||||||||
| const { contentLocale: _removed, ...rest } = raw as Record<string, unknown>; | ||||||||||||||||||||||
| return rest; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+68
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Instruction appended to system prompts so questions, outlines, and drafts | ||||||||||||||||||||||
| * match the user's UI language. | ||||||||||||||||||||||
| * 質問・アウトライン・本文が UI 言語と一致するよう system prompt に付与する。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export function composeContentLocaleInstruction(locale: ComposeContentLocale): string { | ||||||||||||||||||||||
| if (locale === "ja") { | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| "\n\nLanguage: Write all user-facing text (questions, option labels, rationales, " + | ||||||||||||||||||||||
| "outline headings and intents, evaluation rationales, missing aspects, and article " + | ||||||||||||||||||||||
| "body) in Japanese. Use clear, natural Japanese suitable for a wiki article." | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| "\n\nLanguage: Write all user-facing text (questions, option labels, rationales, " + | ||||||||||||||||||||||
| "outline headings and intents, evaluation rationales, missing aspects, and article " + | ||||||||||||||||||||||
| "body) in English." | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Localized conflict-resolution rationale shown in the interrupt UI. | ||||||||||||||||||||||
| * interrupt UI に表示する矛盾解消用の説明文(ロケール別)。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export function composeConflictRationale(locale: ComposeContentLocale): string { | ||||||||||||||||||||||
| if (locale === "ja") { | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| "却下したソースと採用したソースが混在しています。採用したソースのセットで" + | ||||||||||||||||||||||
| "アウトライン生成に進んでよいか確認してください。" | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| "Multiple sources were rejected while others were kept. Confirm you want to proceed " + | ||||||||||||||||||||||
| "with the approved set before generating the outline." | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** Default outline rows when structure LLM fails (locale-specific headings). */ | ||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||
| export function structureDialogueFallbackOutline( | ||||||||||||||||||||||
| locale: ComposeContentLocale, | ||||||||||||||||||||||
| ): Array<{ heading: string; depth: 1; intent: string }> { | ||||||||||||||||||||||
| if (locale === "ja") { | ||||||||||||||||||||||
| return [ | ||||||||||||||||||||||
| { heading: "概要", depth: 1, intent: "トピックの簡潔な導入。" }, | ||||||||||||||||||||||
| { heading: "要点", depth: 1, intent: "主要な事実と背景。" }, | ||||||||||||||||||||||
| { heading: "参考", depth: 1, intent: "出典と関連情報。" }, | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return [ | ||||||||||||||||||||||
| { heading: "Overview", depth: 1, intent: "Brief introduction to the topic." }, | ||||||||||||||||||||||
| { heading: "Key points", depth: 1, intent: "Main facts and context." }, | ||||||||||||||||||||||
| { heading: "References", depth: 1, intent: "Sources and further reading." }, | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||||||||
| * で誤って忘れたケースを早期に検出するため throw する。 | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| import type { LangGraphRunnableConfig } from "@langchain/langgraph"; | ||||||||||||||||||||||
| import { normalizeComposeContentLocale } from "../../../../core/composeLocale.js"; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| GRAPH_CONTEXT_CONFIG_KEY, | ||||||||||||||||||||||
| type GraphContext, | ||||||||||||||||||||||
|
|
@@ -44,5 +45,9 @@ export function getGraphContext(config: LangGraphRunnableConfig | undefined): Gr | |||||||||||||||||||||
| "Check GraphRunner.buildConfig.", | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return ctx as GraphContext; | ||||||||||||||||||||||
| const contentLocale = | ||||||||||||||||||||||
| normalizeComposeContentLocale(ctx.contentLocale) ?? | ||||||||||||||||||||||
| normalizeComposeContentLocale((ctx as { locale?: unknown }).locale) ?? | ||||||||||||||||||||||
| "en"; | ||||||||||||||||||||||
| return { ...(ctx as GraphContext), contentLocale }; | ||||||||||||||||||||||
|
Comment on lines
+48
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an inconsistency in the default fallback locale. In
Suggested change
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current
Accept-Languageresolution only checks the very first language tag in the header. If a user has an unsupported primary language but a supported secondary language (e.g.,Accept-Language: zh-CN,en-US;q=0.9), the parser will fail to matchzh-CNand fall back to the default (ja), ignoring their preference foren.\n\nWe can improve this by splitting the header and checking each language tag in order of preference until a supported language is found.