Skip to content

feat(translation-pipeline): standalone DeepL + LLM + MQM translation package#568

Open
MaxLee-dev wants to merge 78 commits into
mainfrom
deepl-translation-layer
Open

feat(translation-pipeline): standalone DeepL + LLM + MQM translation package#568
MaxLee-dev wants to merge 78 commits into
mainfrom
deepl-translation-layer

Conversation

@MaxLee-dev

@MaxLee-dev MaxLee-dev commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/translation-pipeline — A standalone internal package that translates component JSDoc descriptions from English into Korean.

Deterministic extraction (AST parsing) and non-deterministic translation (LLM invocation) have been separated by the package boundary.

> Scope: Creation of the scripts/translation-pipeline/ package + integration with apps/website i18n scripts.


Pipeline Flow

Initial translation (Sonnet) → MQM evaluation (Opus) → Post-processing on FAIL (Sonnet) → final MQM (Opus)
  • MQM PASS → Cached as ‘verified’ + includes ko/ JSON
  • MQM FAIL (2 times) → Cached as ‘unverified’ + includes ko/ JSON + recorded in report
  • Invalid batch response → Single-item lifecycle fallback + summary in report

Package Structure

src/
├── types.ts
├── defaults.ts
├── translator/
│   ├── translator.ts        ← Orchestrator (batch splitting, fallback, cache management)
│   └── translator.test.ts
├── translation/
│   ├── client.ts            ← LiteLLM HTTP adapter
│   ├── translate.ts         ← Initial translation
│   └── json.ts              ← JSON parsing
├── validation/
│   ├── validator.ts         ← MQM PASS/FAIL determination
│   └── validator.test.ts
├── postprocess/
│   ├── postprocess.ts       ← Correction of stilted translation
│   └── postprocess.test.ts
├── cache/
│   ├── cache.ts             ← Content address cache
│   └── cache.test.ts
├── report/
│   ├── report.ts            ← Markdown quality report
│   └── report.test.ts
└── cli/
    ├── index.ts
    ├── run.ts
    └── run.test.ts
tests/
└── cli.test.ts              ← e2e

Unit tests are located in the same folder as the implementation files (colocation). Only e2e tests are separated into tests/.

Summary of Design Decisions

Decision Details
Package Separation Separate deterministic extraction and non-deterministic translation by package boundary
Input Type Custom-defined TranslatableDocts-api-extractor types not imported
Orchestrator Responsibility translator.ts handles batch splitting and fallback branching exclusively; stage-based modules handle single-item processing only
Cache Storage Timing Saved upon completion of each component (preserving completed parts in case of intermediate failure)
Cache hit/miss classification Classified immediately before processing → Results from preceding components within the same run can be reused
Prompt Mirror MQM FAIL patterns in the initial translation prompt (preventing stilted language in the initial stage)
CI trigger Always run manually — no automatic translation for PRs

Website integration

// apps/website/package.json
"extract":  "ts-api-extractor",
"translate": "translation-pipeline --input ./public/components/generated/en --output ./public/components/generated",
"i18n":     "pnpm extract && pnpm translate"

Output

File Description
ko/*.json Translated Korean JSON
.translation-cache.json Verified translation cache
.i18n-report.md MQM quality report (including unverified and fallback details)

Summary by CodeRabbit

  • New Features
    • Added a translation pipeline and CLI to generate Korean documentation outputs, including caching and a Translation Quality (MQM) report.
    • Introduced an LLM-based postprocessor and MQM validation gate for translation quality.
  • Documentation
    • Updated component JSDoc wording rules and examples to use consistent, role-based templates.
    • Added translation-pipeline documentation and expanded the website .env example with LiteLLM proxy variables.
  • Tests
    • Added end-to-end and unit tests for caching, translation, validation, postprocessing, reporting, and CLI behavior.
  • Chores
    • Adjusted API extraction outputs to write localized prop files under an en directory.

MaxLee-dev and others added 9 commits April 21, 2026 15:14
Implements the core i18n translation layer:
- deepl.ts: DeepL API client with glossary support and graceful fallback
- llm-client.ts: LiteLLM adapter for Sonnet/Opus LLM calls
- llm-postprocess.ts: LLM post-processing with MQM error feedback loop
- mqm-validator.ts: Binary PASS/FAIL MQM quality validator via LiteLLM
- pipeline.ts: Orchestrates DeepL → LLM → MQM per component with cache support
- cache.ts: Content-addressable translation cache keyed by text+locale+model+glossary
- report.ts: MQM report builder with markdown output (buildReport/renderReport/writeReport)
- types.ts: Shared MqmError, MqmResult, and TranslationConfig types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d CLI

- schema.ts: adds TranslationConfig interface with llm/mqm nested config
- defaults.ts: adds translation defaults (disabled by default)
- models/output.ts: adds locale field to PropsOutput for en/ko locale routing
- cli/index.ts: adds --translate flag (enables translation) and --skip-cache flag

Translation is opt-in. Run with --translate to enable DeepL+MQM pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extract.ts: made async; when translation.enabled, writes en/ subfolder
  then calls translatePropsInfo, builds MQM report, writes ko/ output
- stages/write.ts: buildWriteFiles accepts optional locale param;
  when set, output paths become outputDir/{locale}/*.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sts total)

Adds 43+11=54 new test cases covering:
- cache.test.ts: makeCacheKey, loadCache, saveCache (12 tests)
- deepl.test.ts: no-key, success, error, glossary (5 tests)
- llm-postprocess.test.ts: all 7 paths including MQM error feedback
- mqm-validator.test.ts: all 10 paths including malformed JSON
- pipeline.test.ts: empty entries, cache hit/miss, MQM PASS/FAIL, failOnError (9 tests)
- report.test.ts: buildReport, renderReport, writeReport including error path (11 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ncies

- @anthropic-ai/sdk: for LiteLLM-compatible Sonnet/Opus calls in MQM pipeline
- deepl-node or fetch-based: DeepL translation API client
- p-limit: concurrency limiter (LLM_CONCURRENCY=5) for parallel translation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents DEEPL_API_KEY, LITELLM_BASE_URL, LITELLM_MODEL, and
DEEPL_GLOSSARY_ID environment variables needed for the i18n pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntegration

Documents how the JSDoc extraction pipeline now supports --translate flag
for automatic DeepL + MQM translation to Korean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cache

- mqm-validator.ts: validate LLM JSON output shape before casting to MqmResult;
  malformed responses (missing verdict/errors fields) safely return PASS
- cache.ts: filter cache entries missing the translated field on load;
  prevents undefined propagation if cache file is from an older schema version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l edge cases

pipeline.ts:
- Skip LLM post-processing when DeepL falls back to source text (prevents
  sending English to an LLM asked to edit Korean, which produces garbage)
- Clear MQM errors after successful retry so .i18n-report.md reflects the
  actual final translation, not the pre-retry state
- Don't cache fallback entries (source == translation) to prevent DeepL
  rate-limit failures from permanently poisoning the cache with English text
- failCount now counts individual MQM errors per component, not 0/1 boolean
  (fixes misleading pass rate when a component has multiple failures)

mqm-validator.ts:
- passResult() factory function instead of shared PASS_RESULT singleton to
  prevent accidental mutation of the shared errors array

report.ts:
- Escape bracket characters in e.message to prevent markdown link injection
  from LLM-generated error messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MaxLee-dev MaxLee-dev requested a review from noahchoii as a code owner April 21, 2026 06:25
@vercel

vercel Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vapor-ui Ready Ready Preview, Comment Jun 17, 2026 7:46am

Request Review

@changeset-bot

changeset-bot Bot commented Apr 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d63f6bd

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@MaxLee-dev, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 9 minutes and 47 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eb2c3a44-a834-45da-b1d6-65f7c77fce93

📥 Commits

Reviewing files that changed from the base of the PR and between b96d2f4 and d63f6bd.

📒 Files selected for processing (20)
  • scripts/translation-pipeline/CLAUDE.md
  • scripts/translation-pipeline/README.md
  • scripts/translation-pipeline/src/cache/cache.test.ts
  • scripts/translation-pipeline/src/cache/cache.ts
  • scripts/translation-pipeline/src/cli/run.ts
  • scripts/translation-pipeline/src/defaults.ts
  • scripts/translation-pipeline/src/report/report.test.ts
  • scripts/translation-pipeline/src/report/report.ts
  • scripts/translation-pipeline/src/translation/client.test.ts
  • scripts/translation-pipeline/src/translation/client.ts
  • scripts/translation-pipeline/src/translation/json.ts
  • scripts/translation-pipeline/src/translation/translate.test.ts
  • scripts/translation-pipeline/src/translation/translate.ts
  • scripts/translation-pipeline/src/translator/batch-lifecycle.ts
  • scripts/translation-pipeline/src/translator/translator.test.ts
  • scripts/translation-pipeline/src/translator/translator.ts
  • scripts/translation-pipeline/src/types.ts
  • scripts/translation-pipeline/src/validation/validator.test.ts
  • scripts/translation-pipeline/src/validation/validator.ts
  • scripts/translation-pipeline/tests/cli.test.ts
📝 Walkthrough

Walkthrough

Adds a complete LLM-driven translation pipeline that translates English API documentation JSON into Korean, validates output quality using MQM evaluation, postprocesses failing translations, caches verified results, generates Markdown quality reports, and provides a CLI. Also updates JSDoc guidance rules, ts-api-extractor output structure, website configuration, and workspace integration.

Changes

Documentation & Tooling Updates

Layer / File(s) Summary
JSDoc component summary wording
.claude/skills/write-component-jsdocs/SKILL.md, .../references/guide.md
Replaced generic "user perspective" guidance with role-based JSDoc templates for standalone/sub/root components; require summaries to end with rendered HTML tag in backticks (except root/provider components: exact text Doesn't render its own HTML element.); removed default-value Default: checklist guidance; added prop wording patterns by category (boolean, event handlers, numeric).
ts-api-extractor output & versioning
scripts/ts-api-extractor/src/extract.ts, scripts/ts-api-extractor/package.json
Updated props file write target from outputDir to outputDir/en; adjusted dependency/devDependency versions for glob, meow; lowered minimum TypeScript peerDependency and Node engine versions.
Website docs extraction integration
apps/website/docs-extractor.config.mjs, apps/website/package.json
Added translation LLM model config entries (postprocessModel, validationModel) to docs-extractor; removed inline --config argument from extract npm script; added @vapor-ui/translation-pipeline as devDependency.
Website environment & versioning
apps/website/.env.example, apps/website/.gitignore
Added LITELLM_BASE_URL and LITELLM_API_KEY environment variables with setup comments; added **/.i18n-report.md to gitignore for generated reports.
Workspace registration
pnpm-workspace.yaml
Added scripts/translation-pipeline to workspace packages list.

Translation Pipeline Package

Layer / File(s) Summary
Type schema
scripts/translation-pipeline/src/types.ts
Defines translatable document model (name, optional description, props array), MQM error/result types (categories, severities, error details), translation unit identification (kind, source, indices), and outcome lifecycle types (assurance, reason, events) plus TranslationConfig.
LLM client & utilities
scripts/translation-pipeline/src/translation/client.ts, .../json.ts, .../client.test.ts
Implements callLlm with environment validation, 60-second timeout, optional JSON response formatting, response-cost and token-usage extraction; adds parseLlmJson helper for fence-unwrapping; comprehensive tests for request/response behavior and metadata parsing.
LLM-based translation
scripts/translation-pipeline/src/translation/translate.ts, .../translate.test.ts
Implements translateComponentUnits with strict system prompt enforcing Korean tone (합쇼체), identifier/token preservation, and JSON-only output; validates response structure (translations[]), enforces complete coverage, rejects duplicates/empty translations; tests verify prompt content, fence parsing, and validation errors.
MQM quality validation
scripts/translation-pipeline/src/validation/validator.ts, .../validator.test.ts
Implements validateWithMqm with MQM evaluator prompt returning PASS/FAIL verdicts and structured error objects; includes isMqmError type guard and robust JSON parsing with fallback extraction; tests cover payload parsing, error categorization, and failure fallbacks.
Postprocessing with feedback
scripts/translation-pipeline/src/postprocess/postprocess.ts, .../postprocess.test.ts
Implements postprocessWithLlm to rewrite machine translations using source, initial draft, and numbered MQM error feedback; enforces JSON output with translated field; returns { translated, invalid?: true } on failures; tests validate request shape, fence parsing, and error handling.
Translation result caching
scripts/translation-pipeline/src/cache/cache.ts, .../cache.test.ts
Implements makeCacheKey using SHA-256 over source + cache version + locale + model IDs; loadCache reads/validates .translation-cache.json with graceful empty fallback; saveCache persists with parent-dir creation and non-throwing error logging; tests cover determinism, persistence, and failure modes.
Batch lifecycle orchestration
scripts/translation-pipeline/src/translator/batch-lifecycle.ts
Implements processComponentLifecycle orchestrating chunked batch MQM evaluation, routing PASS units to outcomes, batching failed units for postprocessing, final MQM re-validation, and constructing TranslationOutcome objects with assurance/reportability, reason codes, and event logs; handles degraded states from batch failures.
Translation pipeline orchestrator
scripts/translation-pipeline/src/translator/translator.ts, .../translator.test.ts
Implements translatePropsInfo entrypoint: collects units from component/prop descriptions, optionally loads/saves per-component cache, splits cache hits vs misses, batches misses through translateComponentUnits + lifecycle, caches verified outcomes, applies results to original props, builds component reports; tests verify caching, batching, degradation handling, multi-component sequencing.
Quality report generation
scripts/translation-pipeline/src/report/report.ts, .../report.test.ts
Implements buildComponentReports aggregating per-component metrics, buildReport assembling full metadata with timestamp, renderReport generating Markdown with summary table/batch fallbacks/component details/MQM error tables, and writeReport persisting .i18n-report.md; tests cover rendering, aggregation, and failure modes.
CLI entry point & runner
scripts/translation-pipeline/src/cli/index.ts, .../run.ts, .../run.test.ts, tests/cli.test.ts
Implements async CLI bootstrap with optional .env loading, parseCliArgs using meow for --input/--output, run function reading/normalizing EN JSON, invoking translation with test-override support, merging results into raw docs, writing ko/ outputs and report; includes CliError for structured errors; tests cover arg/env validation and e2e scenarios including MQM FAIL→postprocess→PASS.
Build & test tooling
scripts/translation-pipeline/package.json, tsconfig.json, tsup.config.ts, vitest.config.ts, eslint.config.mjs
New private ESM package with CLI binary entry (translation-pipeline), build/test/format/lint scripts; TypeScript config with ~/* alias to src; tsup ESM+sourcemaps build for Node 20 with shebang; Vitest with v8 coverage and node environment; ESLint config re-export from shared base.
Developer documentation
scripts/translation-pipeline/CLAUDE.md, README.md, handoff.md
CLAUDE.md documents commands, architecture, module responsibilities, and design constraints (MQM mirroring, cache key composition, degraded outcomes, cache write timing); README.md describes pipeline purpose, flow, requirements, CLI usage, translation scope, expected outputs, and project structure; handoff.md records branch state, commit history, and session notes.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI
    participant Parser as parseCliArgs
    participant Reader as readEnJsonFiles
    participant Cache as Cache Layer
    participant Translator as Orchestrator
    participant LLMTrans as callLlm (translate)
    participant MQMValidator as callLlm (MQM)
    participant Postproc as Postprocessor
    participant Reporter as Report Gen
    participant Writer as File Writer

    CLI->>Parser: argv
    Parser->>CLI: input, output
    CLI->>Reader: read en/*.json
    Reader->>CLI: TranslatableDoc[]
    CLI->>Cache: loadCache(outputDir)
    Cache->>CLI: CacheStore
    CLI->>Translator: translatePropsInfo(props)
    loop per component / per batch
      Translator->>Translator: collectTranslationUnits()
      Translator->>LLMTrans: translateComponentUnits(units)
      LLMTrans->>CLI: translations Map
      Translator->>MQMValidator: validateBatchWithMqm(translated)
      MQMValidator->>MQMValidator: callLlm (MQM prompt)
      alt PASS verdict
        Translator->>Translator: initialPassOutcome
      else FAIL verdict
        Translator->>Postproc: postprocessBatchWithLlm(failed)
        Postproc->>Postproc: callLlm (rewrite prompt)
        Translator->>MQMValidator: validateBatchWithMqm(rewritten)
        Translator->>Translator: finalOutcome or degraded
      end
      Translator->>Cache: saveCache(verified)
    end
    Translator->>Translator: applyTranslationOutcomes()
    Translator->>Reporter: buildReport(components)
    Reporter->>Reporter: renderReport()
    CLI->>Writer: writeKoFiles(merged JSON)
    Writer->>CLI: writtenFiles[]
    CLI->>Writer: writeReport(report)
    Writer->>CLI: reportPath
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • goorm-dev/vapor-ui#564: Modifies the same .claude/skills/write-component-jsdocs/ JSDoc wording rules and component summary guidance files.
  • goorm-dev/vapor-ui#423: Updates ts-api-extractor and docs extraction configuration that this PR builds upon with translation-pipeline integration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deepl-translation-layer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.claude/skills/write-component-jsdocs/references/guide.md (1)

427-468: ⚠️ Potential issue | 🟠 Major

Update the Complete Example to match the new patterns.

The Complete Example section hasn't been updated to follow the new "Wording patterns by component role" and "Wording patterns by prop category" introduced earlier in this PR:

  1. Line 463 — The component summary uses multiple sentences: "Button component for user interactions. Use for primary actions such as form submission, dialog triggers, and navigation. Renders a <button> element." but the new pattern (line 206) requires one single line. The new top-level component pattern (line 164) specifies: "A [noun phrase] for/that [purpose]." ending with the rendered element.

  2. Line 457 — The event handler description reads "Called when the button is clicked..." but the new event handler pattern (line 301) requires: "Event handler called when [exact condition]."

These inconsistencies will confuse developers who read the Complete Example expecting it to demonstrate best practices.

📝 Proposed updates to align with new patterns
 /**
- * Button component for user interactions. Use for primary actions such as form submission, dialog triggers, and navigation. Renders a `<button>` element.
+ * A button for triggering actions and submitting forms. Renders a `<button>` element.
  */
 export function Button({ label, variant = 'fill', size = 'md', ...props }: ButtonProps) {
     /**
-     * Called when the button is clicked or activated via Enter or Space key.
+     * Event handler called when the button is clicked or activated via Enter or Space key.
      */
     onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/write-component-jsdocs/references/guide.md around lines 427 -
468, Update the JSDoc in the Complete Example to follow the new single-line
component and event wording patterns: change the Button component summary to a
single sentence following "A [noun phrase] for/that [purpose]." and end with the
rendered element (e.g., "A button for primary user actions that renders a
<button> element."), and change the onClick prop JSDoc in ButtonProps to the
event handler pattern "Event handler called when [exact condition]." (e.g.,
"Event handler called when the button is clicked or activated via Enter or Space
key."). Ensure these changes are applied to the Button (component summary) and
onClick (prop) entries.
scripts/ts-api-extractor/src/config/schema.ts (1)

37-96: ⚠️ Potential issue | 🟠 Major

Validate or deep-merge the new translation config before use.

translation is now accepted on ExtractorConfig, but validatePartialConfig never checks its nested shape and mergeConfig replaces it shallowly. A runtime config like { translation: { enabled: true } } can pass validation, overwrite defaults, and later crash when code reads config.translation.llm.enabled or config.translation.validation.mqm.enabled.

🛡️ Proposed hardening direction
+function mergeTranslationConfig(
+    base: ExtractorConfig['translation'],
+    patch: PartialExtractorConfig['translation'],
+): ExtractorConfig['translation'] {
+    if (patch === undefined) return base;
+    if (base === undefined) return patch;
+
+    return {
+        ...base,
+        ...patch,
+        llm: {
+            ...base.llm,
+            ...patch.llm,
+        },
+        validation: {
+            ...base.validation,
+            ...patch.validation,
+            mqm: {
+                ...base.validation.mqm,
+                ...patch.validation?.mqm,
+            },
+        },
+    };
+}
+
 export function mergeConfig(base: ExtractorConfig, patch: PartialExtractorConfig): ExtractorConfig {
     return {
         ...base,
         ...patch,
         exclude: patch.exclude ?? base.exclude,
         includeHtml: patch.includeHtml ?? base.includeHtml,
+        translation: mergeTranslationConfig(base.translation, patch.translation),
         components: {
             ...base.components,
             ...(patch.components ?? {}),
         },
     };
 }

Also add runtime validation for translation.enabled, translation.targetLocale, translation.llm.enabled, and translation.validation.mqm.{enabled,failOnError} when translation is present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/src/config/schema.ts` around lines 37 - 96,
validatePartialConfig currently skips any checks for config.translation and
mergeConfig shallowly replaces translation, allowing invalid or partial shapes
to slip through; add runtime validation inside validatePartialConfig for the
translation object and its nested keys (check translation.enabled boolean,
translation.targetLocale string if present, translation.llm.enabled boolean if
llm present, and translation.validation.mqm.enabled and
translation.validation.mqm.failOnError booleans when validation/mqm are present)
and make mergeConfig perform a deep-merge for the translation key (merge
base.translation with patch.translation rather than overwriting) so nested
defaults are preserved; update references to validation and merge logic in
validatePartialConfig and mergeConfig accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/.env.example`:
- Around line 8-11: The dotenv entries are out of dotenv-linter's expected
alphabetical/order and trigger a lint error; reorder the LITELLM_* variables so
LITELLM_API_KEY appears before LITELLM_BASE_URL (keeping LITELLM_MODEL as
needed) in the .env.example so dotenv-linter passes—update the block containing
LITELLM_API_KEY, LITELLM_BASE_URL, and LITELLM_MODEL accordingly.

In `@scripts/ts-api-extractor/src/config/defaults.ts`:
- Around line 24-36: The translation defaults object in defaults.ts (the
translation block with enabled/targetLocale/llm/validation.mqm) is being
shallow-overwritten by mergeConfig(); update mergeConfig() to perform a
deep/recursive merge for the "translation" key (or use a deep-merge util such as
lodash.merge) so partial user config like translation.validation.mqm.failOnError
only overrides that nested field and preserves defaults.enabled, targetLocale
and llm; locate mergeConfig() and change its logic to deep-merge the incoming
config into the defaults (or add a special-case deep merge for the translation
property) to ensure nested defaults remain intact.

In `@scripts/ts-api-extractor/src/extract.ts`:
- Around line 83-95: The code only assigns writtenFiles to the result of writing
the translated (ko) props, dropping the earlier en outputs; update the logic in
the config.translation?.enabled branch so writtenFiles includes both the
original English writePropsFiles(...) call result and the translated
writePropsFiles(...) result (e.g. combine/concat the returned arrays/counts from
the first writePropsFiles(props, outputDir, formatFileName, 'en') and the later
writePropsFiles(translatedProps, outputDir, formatFileName, 'ko')), leaving
translatePropsInfo and buildReport usage unchanged.

In `@scripts/ts-api-extractor/src/translate/deepl.ts`:
- Around line 8-11: The current DeepL gate returns the original texts when no
API key or on failure, making outages indistinguishable from legitimate
translations; instead, change those early-return paths (the branch checking
apiKey and the other failure branches around lines 30-42) to return undefined
(or null) so the pipeline's deeplResult === undefined check can detect a DeepL
fallback; update any console.warn/error messages to note "falling back (no
DeepL)" and ensure the function that returns texts uses the same undefined
signal across all failure branches.
- Around line 20-28: The DeepL fetch call in the try block should use an
AbortController with a bounded timeout to avoid hanging; create an
AbortController, start a timer (e.g., setTimeout) that calls controller.abort()
after the desired ms, pass controller.signal into the fetch options (in the
request started in this try), and ensure the timeout is cleared in a finally
block (wrap clearTimeout(timeout) inside finally so the timer is always
released) around the fetch/response handling code in deepl.ts.

In `@scripts/ts-api-extractor/src/translate/llm-client.ts`:
- Around line 34-41: The code currently casts response.json() to a shape and
returns data.choices?.[0]?.message?.content without validating its runtime type;
update the extraction around the data and content variables so you check that
data.choices?.[0]?.message?.content is a string (e.g., typeof content ===
'string') before returning it, and if it's missing or not a string return {
content: null, error: 'Unexpected response shape' } instead; adjust the block
that computes content and the subsequent return in llm-client.ts (the
variables/data referenced as data and content) accordingly.
- Around line 12-28: Validate both LITELLM_BASE_URL and LITELLM_API_KEY before
calling fetch (return an error if either is missing rather than sending
Authorization: Bearer undefined), and wrap the fetch call in an AbortController
with a timeout: create an AbortController, pass controller.signal to fetch, set
a timer to call controller.abort() after a configurable timeout, and ensure you
clearTimeout(timeout) in a finally block; update the code around the existing
fetch invocation (the POST to `${baseUrl}/chat/completions` that sends model and
messages) to use the controller.signal and abort/cleanup logic.

In `@scripts/ts-api-extractor/src/translate/llm-postprocess.ts`:
- Around line 35-44: The current post-processing returns result.content after
stripping code fences but if that yields an empty string it mistakenly replaces
a valid deeplDraft; modify the logic in the LLM postprocess block (the code
handling result.content and deeplDraft) to assign the cleaned string to a
variable (e.g., cleaned) after the .replace(...).trim() sequence and then return
cleaned || deeplDraft so that an empty or whitespace-only LLM response falls
back to deeplDraft.

In `@scripts/ts-api-extractor/src/translate/mqm-validator.ts`:
- Around line 53-62: The parsed payload validation in mqm-validator.ts is too
permissive: update the check that currently returns passResult() to also enforce
that (parsed as MqmResult).verdict is exactly "PASS" or "FAIL" and that (parsed
as MqmResult).errors is an array whose entries match the expected error shape
(e.g., each entry is an object with required properties like "message" and
"severity" or whatever fields MqmResult defines); if the verdict is not one of
the two allowed values or any error entry is malformed, log a warning and return
passResult() to avoid trusting malformed payloads. Ensure you reference the
existing symbols parsed, MqmResult, and passResult() when implementing the
stricter validation so downstream logic never receives arbitrary strings like
"OK".

In `@scripts/ts-api-extractor/src/translate/pipeline.ts`:
- Around line 100-116: translateWithDeepl can return fallback originals which
must not be treated as valid DeepL drafts; update translateWithDeepl to return
metadata per item (e.g., { text, usedFallback }) and then in pipeline.ts (the
block using translateWithDeepl, missIndices, deeplResults, postprocessWithLlm
and limit) change the check from deeplResult === undefined to check
deeplResult.usedFallback (or similar) and when usedFallback is true log a
warning and return the source entries[entryIndex].text without calling
postprocessWithLlm; ensure all callers of translateWithDeepl are updated to
handle the new result shape.
- Around line 132-158: When an MQM FAIL occurs the code currently never records
the failure into mqmErrorsByEntryIdx and the retry path can delete errors
incorrectly; update the flow in the block handling mqmResult.verdict === 'FAIL'
so that you (1) record the initial failure via
mqmErrorsByEntryIdx.set(entryIndex, mqmResult.errors), (2) when you run the
retry via recheck = await validateWithMqm(...) replace the stored errors with
recheck.errors if recheck.verdict === 'FAIL', or delete mqmErrorsByEntryIdx
entry only if recheck.verdict !== 'FAIL' (i.e. retry passed), and (3) in the
failOnError true branch before throwing ensure mqmErrorsByEntryIdx contains the
final recheck.errors so the report reflects the ultimate outcome; use the
existing symbols mqmErrorsByEntryIdx, entryIndex, mqmResult, recheck,
validateWithMqm and config.validation.mqm.failOnError to locate and implement
these changes.

In `@scripts/ts-api-extractor/src/translate/report.ts`:
- Around line 21-27: buildReport currently uses failCount computed from MQM
error arrays (errors.length) which overcounts failures because one text can
yield multiple MqmError entries; update buildReport (and any aggregation logic
that sets failCount in TranslationReport) to either (A) compute a distinct
failed-text count by de-duplicating failures per text id (e.g., mark a
component's failedTextCount as count of unique text identifiers with errors and
sum those into failCount) or (B) rename/keep failCount as mqmErrorCount and add
a separate failedTexts field used for pass-rate math; adjust totalTexts,
pass-rate calculation, and FAIL (${failCount}/${totalTexts}) rendering to use
the new failedTexts metric instead of raw MQM error counts (reference
buildReport, failCount, totalTexts, components, and ComponentReport where errors
are stored).
- Around line 33-41: The loop that builds Markdown lines in translate/report.ts
currently only escapes backticks/brackets for source, translation and message,
which allows newlines and other Markdown control characters to inject headings
or list items; update the c.errors iteration (where source, translation, message
are computed and used in lines.push) to run all three values through a sanitizer
(e.g., sanitizeMarkdown) that 1) strips or replaces newlines with spaces, and 2)
escapes common Markdown/control chars (#, -, *, _, >, `, [, ], (, ), +, and
leading/trailing whitespace) so the final template string in lines.push cannot
create headings or bullets. Implement the sanitizer as a small helper and call
it for e.source, e.translation and e.message before formatting the output.

In `@scripts/ts-api-extractor/test/translate/cache.test.ts`:
- Around line 116-120: The test "does not throw when outputDir is unwritable
(logs warning instead)" is non-deterministic; instead create a real temporary
file (e.g., via fs.writeFileSync on a unique tmp path) and pass that file path
into saveCache so the subsequent mkdirSync(dirname(...)) in saveCache will
reliably fail; assert saveCache does not throw, that console.warn was called,
and then remove the temp file in cleanup. Reference: saveCache and the
mkdirSync(dirname(...)) behavior to locate where to trigger the failure.

In `@scripts/ts-api-extractor/test/translate/pipeline.test.ts`:
- Around line 121-154: The test currently only checks that a warning was emitted
and a result exists but does not assert the MQM report contents; update the test
for the FAIL path (the test using translatePropsInfo, sampleProps, baseConfig
and mqmModule.mockResolvedValue) to assert the generated componentReports
contain the MQM failure details by verifying
result.componentReports[0].failCount is > 0 (or equals 1) and that
result.componentReports[0].errors includes the mocked error (e.g. an entry with
severity 'major' and type 'mistranslation'); do the same for the other similar
tests mentioned (lines ~192-231) so the spec verifies the report data consumed
by .i18n-report.md rather than only the warning/result presence.

In `@scripts/ts-api-extractor/test/translate/report.test.ts`:
- Around line 147-158: The test using chmod to make readonlyDir non-writable is
flaky; instead make the write failure deterministic by creating a file where the
directory is expected so mkdirSync(dirname(filePath)) fails: in the 'warns and
does not throw when write fails' test, replace creating a directory with mode
0o444 with creating a regular file at readonlyDir (or creating a file at
path.join(tmpDir, 'readonly', 'placeholder') so the parent mkdir will fail),
call buildReport and pass that path into writeReport as before, assert the same
warnSpy behavior, then restore the spy and remove the file to clean up;
reference functions/variables writeReport, buildReport, readonlyDir, tmpDir,
warnSpy, and the mkdirSync(dirname(filePath)) failure point when making the
change.

---

Outside diff comments:
In @.claude/skills/write-component-jsdocs/references/guide.md:
- Around line 427-468: Update the JSDoc in the Complete Example to follow the
new single-line component and event wording patterns: change the Button
component summary to a single sentence following "A [noun phrase] for/that
[purpose]." and end with the rendered element (e.g., "A button for primary user
actions that renders a <button> element."), and change the onClick prop JSDoc in
ButtonProps to the event handler pattern "Event handler called when [exact
condition]." (e.g., "Event handler called when the button is clicked or
activated via Enter or Space key."). Ensure these changes are applied to the
Button (component summary) and onClick (prop) entries.

In `@scripts/ts-api-extractor/src/config/schema.ts`:
- Around line 37-96: validatePartialConfig currently skips any checks for
config.translation and mergeConfig shallowly replaces translation, allowing
invalid or partial shapes to slip through; add runtime validation inside
validatePartialConfig for the translation object and its nested keys (check
translation.enabled boolean, translation.targetLocale string if present,
translation.llm.enabled boolean if llm present, and
translation.validation.mqm.enabled and translation.validation.mqm.failOnError
booleans when validation/mqm are present) and make mergeConfig perform a
deep-merge for the translation key (merge base.translation with
patch.translation rather than overwriting) so nested defaults are preserved;
update references to validation and merge logic in validatePartialConfig and
mergeConfig accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a6fbd0c-3cca-475d-b9de-1d57cbe1fea2

📥 Commits

Reviewing files that changed from the base of the PR and between 250b914 and 4496269.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (24)
  • .claude/skills/write-component-jsdocs/SKILL.md
  • .claude/skills/write-component-jsdocs/references/guide.md
  • apps/website/.env.example
  • scripts/ts-api-extractor/package.json
  • scripts/ts-api-extractor/src/cli/index.ts
  • scripts/ts-api-extractor/src/config/defaults.ts
  • scripts/ts-api-extractor/src/config/schema.ts
  • scripts/ts-api-extractor/src/extract.ts
  • scripts/ts-api-extractor/src/models/output.ts
  • scripts/ts-api-extractor/src/stages/write.ts
  • scripts/ts-api-extractor/src/translate/cache.ts
  • scripts/ts-api-extractor/src/translate/deepl.ts
  • scripts/ts-api-extractor/src/translate/llm-client.ts
  • scripts/ts-api-extractor/src/translate/llm-postprocess.ts
  • scripts/ts-api-extractor/src/translate/mqm-validator.ts
  • scripts/ts-api-extractor/src/translate/pipeline.ts
  • scripts/ts-api-extractor/src/translate/report.ts
  • scripts/ts-api-extractor/src/translate/types.ts
  • scripts/ts-api-extractor/test/translate/cache.test.ts
  • scripts/ts-api-extractor/test/translate/deepl.test.ts
  • scripts/ts-api-extractor/test/translate/llm-postprocess.test.ts
  • scripts/ts-api-extractor/test/translate/mqm-validator.test.ts
  • scripts/ts-api-extractor/test/translate/pipeline.test.ts
  • scripts/ts-api-extractor/test/translate/report.test.ts
💤 Files with no reviewable changes (1)
  • scripts/ts-api-extractor/src/models/output.ts

Comment thread scripts/ts-api-extractor/src/config/defaults.ts Outdated
Comment thread scripts/ts-api-extractor/src/extract.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/deepl.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/deepl.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/llm-client.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/report.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/report.ts Outdated
Comment thread scripts/ts-api-extractor/test/translate/cache.test.ts Outdated
Comment thread scripts/ts-api-extractor/test/translate/pipeline.test.ts Outdated
Comment thread scripts/ts-api-extractor/test/translate/report.test.ts
…pipeline

- extract.ts: include both en and ko writtenFiles in translate mode
- deepl.ts: return undefined on failure (signal fallback), add 10s AbortController timeout
- llm-client.ts: fail fast on missing LITELLM_API_KEY, add 30s timeout, strict typeof content check
- llm-postprocess.ts: fallback to deeplDraft when cleaned LLM output is empty
- mqm-validator.ts: enforce verdict === 'PASS'|'FAIL' and validate error entry shape
- pipeline.ts: record MQM failures into mqmErrorsByEntryIdx before retry; clear only on retry success; count failed texts (not error entries) for failCount
- report.ts: sanitize Markdown special chars and newlines before injecting into report
- tests: update deepl expectations to undefined, make cache/report write-failure tests deterministic, assert componentReports MQM content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/ts-api-extractor/src/translate/pipeline.ts (1)

152-165: ⚠️ Potential issue | 🟡 Minor

Report accuracy when failOnError is false.

When failOnError is false, the retry runs (lines 144-150) but no recheck validates whether the retry succeeded. The errors recorded at line 142 persist in the report regardless of whether the retry actually fixed the translation.

Consider running the recheck unconditionally and only throwing when failOnError && recheck.verdict === 'FAIL'. This ensures the report accurately reflects the final translation quality.

🔧 Proposed fix
                         translated = await limit(() =>
                             postprocessWithLlm(
                                 entry.text,
                                 deeplDraft ?? entry.text,
                                 mqmResult.errors,
                             ),
                         );

-                        if (config.validation.mqm.failOnError) {
-                            const recheck = await limit(() =>
-                                validateWithMqm(entry.text, translated, config),
-                            );
-                            if (recheck.verdict === 'FAIL') {
+                        const recheck = await limit(() =>
+                            validateWithMqm(entry.text, translated, config),
+                        );
+
+                        if (recheck.verdict === 'FAIL') {
+                            mqmErrorsByEntryIdx.set(entryIndex, recheck.errors);
+                            if (config.validation.mqm.failOnError) {
                                 mqmErrorsByEntryIdx.set(entryIndex, recheck.errors);
                                 throw new Error(
                                     `[mqm-validator] Translation validation FAILED after retry for: "${entry.text.slice(0, 60)}..."`,
                                 );
-                            } else {
-                                mqmErrorsByEntryIdx.delete(entryIndex);
                             }
+                        } else {
+                            mqmErrorsByEntryIdx.delete(entryIndex);
                         }
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/src/translate/pipeline.ts` around lines 152 - 165,
When a retry is performed, always run the recheck via validateWithMqm (using the
same limit(...) call) and update mqmErrorsByEntryIdx based on recheck.verdict;
only throw an Error when config.validation.mqm.failOnError is true AND
recheck.verdict === 'FAIL'. In other words, call validateWithMqm unconditionally
for the retry, set mqmErrorsByEntryIdx.set(entryIndex, recheck.errors) if
recheck.verdict === 'FAIL' else mqmErrorsByEntryIdx.delete(entryIndex), and only
throw inside the block when config.validation.mqm.failOnError && recheck.verdict
=== 'FAIL' (references: validateWithMqm, mqmErrorsByEntryIdx,
config.validation.mqm.failOnError, limit).
🧹 Nitpick comments (3)
scripts/ts-api-extractor/test/translate/deepl.test.ts (2)

10-10: Remove unused DEEPL_GLOSSARY_ID env stubs in this suite.

Line 10 and Line 37 stub an env var that translateWithDeepl does not read (it uses the glossaryId argument). Keeping these stubs can mislead future readers about behavior under test.

Diff suggestion
     beforeEach(() => {
         vi.stubGlobal('fetch', vi.fn());
         vi.stubEnv('DEEPL_ENDPOINT', 'https://api-free.deepl.com/v2/translate');
         vi.stubEnv('DEEPL_API_KEY', 'test-api-key');
-        vi.stubEnv('DEEPL_GLOSSARY_ID', '');
     });
@@
     it('glossary_id 미설정 시 body에 glossary_id 없음', async () => {
-        vi.stubEnv('DEEPL_GLOSSARY_ID', '');
-
         const mockFetch = vi.mocked(fetch);

Also applies to: 37-37

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/test/translate/deepl.test.ts` at line 10, The test
suite stubs DEEPL_GLOSSARY_ID even though translateWithDeepl reads the
glossaryId argument (not that env var); remove the unnecessary
vi.stubEnv('DEEPL_GLOSSARY_ID', '') calls from this test file (the occurrences
near the top of the suite and the one around line 37) so tests no longer mock an
unused environment variable and avoid misleading readers—leave other stubs
intact and ensure translateWithDeepl invocation still passes the intended
glossaryId argument.

81-92: Add an explicit non-OK HTTP response test for the fallback branch.

You already test rejected fetch; adding a response.ok === false case will directly lock coverage for the status-based fallback path in deepl.ts.

Diff suggestion
     it('fetch 실패 시 undefined 반환 + warn', async () => {
         const mockFetch = vi.mocked(fetch);
         mockFetch.mockRejectedValueOnce(new Error('Network error'));
         const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

         const texts = ['Hello'];
         const result = await translateWithDeepl(texts, '');

         expect(result).toBeUndefined();
         expect(warnSpy).toHaveBeenCalledOnce();
     });
+
+    it('HTTP non-ok 응답 시 undefined 반환 + warn', async () => {
+        const mockFetch = vi.mocked(fetch);
+        mockFetch.mockResolvedValueOnce({
+            ok: false,
+            status: 429,
+            json: async () => ({}),
+        } as Response);
+        const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
+
+        const result = await translateWithDeepl(['Hello'], '');
+
+        expect(result).toBeUndefined();
+        expect(warnSpy).toHaveBeenCalledOnce();
+    });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/test/translate/deepl.test.ts` around lines 81 - 92,
Add a test in deepl.test.ts that covers the non-OK HTTP response fallback: mock
the global fetch used by translateWithDeepl so it resolves to a response-like
object with ok: false (and a status/code/message), spy on console.warn, call
translateWithDeepl(['Hello'], ''), and assert the function returns undefined and
console.warn was called; this will exercise the status-based branch in deepl.ts
instead of the rejected-fetch branch already covered.
scripts/ts-api-extractor/src/translate/llm-client.ts (1)

26-26: Normalize trailing slash from baseUrl.

If LITELLM_BASE_URL ends with a trailing slash (e.g., https://api.example.com/), the constructed URL becomes https://api.example.com//chat/completions with a double slash. While most servers handle this gracefully, normalizing prevents potential routing issues.

🔧 Proposed fix
-            response = await fetch(`${baseUrl}/chat/completions`, {
+            response = await fetch(`${baseUrl.replace(/\/+$/, '')}/chat/completions`, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/src/translate/llm-client.ts` at line 26, The fetch
URL can end up with a double slash when LITELLM_BASE_URL has a trailing slash;
normalize baseUrl before using it in the fetch call by trimming any trailing
slashes (e.g., compute baseUrl = LITELLM_BASE_URL.replace(/\/+$/, "") or
similar) and then use that normalized baseUrl in the existing
fetch(`${baseUrl}/chat/completions`, ...) call (update the code around the
fetch/response logic in llm-client.ts to use the trimmed baseUrl variable).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@scripts/ts-api-extractor/src/translate/pipeline.ts`:
- Around line 152-165: When a retry is performed, always run the recheck via
validateWithMqm (using the same limit(...) call) and update mqmErrorsByEntryIdx
based on recheck.verdict; only throw an Error when
config.validation.mqm.failOnError is true AND recheck.verdict === 'FAIL'. In
other words, call validateWithMqm unconditionally for the retry, set
mqmErrorsByEntryIdx.set(entryIndex, recheck.errors) if recheck.verdict ===
'FAIL' else mqmErrorsByEntryIdx.delete(entryIndex), and only throw inside the
block when config.validation.mqm.failOnError && recheck.verdict === 'FAIL'
(references: validateWithMqm, mqmErrorsByEntryIdx,
config.validation.mqm.failOnError, limit).

---

Nitpick comments:
In `@scripts/ts-api-extractor/src/translate/llm-client.ts`:
- Line 26: The fetch URL can end up with a double slash when LITELLM_BASE_URL
has a trailing slash; normalize baseUrl before using it in the fetch call by
trimming any trailing slashes (e.g., compute baseUrl =
LITELLM_BASE_URL.replace(/\/+$/, "") or similar) and then use that normalized
baseUrl in the existing fetch(`${baseUrl}/chat/completions`, ...) call (update
the code around the fetch/response logic in llm-client.ts to use the trimmed
baseUrl variable).

In `@scripts/ts-api-extractor/test/translate/deepl.test.ts`:
- Line 10: The test suite stubs DEEPL_GLOSSARY_ID even though translateWithDeepl
reads the glossaryId argument (not that env var); remove the unnecessary
vi.stubEnv('DEEPL_GLOSSARY_ID', '') calls from this test file (the occurrences
near the top of the suite and the one around line 37) so tests no longer mock an
unused environment variable and avoid misleading readers—leave other stubs
intact and ensure translateWithDeepl invocation still passes the intended
glossaryId argument.
- Around line 81-92: Add a test in deepl.test.ts that covers the non-OK HTTP
response fallback: mock the global fetch used by translateWithDeepl so it
resolves to a response-like object with ok: false (and a status/code/message),
spy on console.warn, call translateWithDeepl(['Hello'], ''), and assert the
function returns undefined and console.warn was called; this will exercise the
status-based branch in deepl.ts instead of the rejected-fetch branch already
covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e3ab9178-e82b-492c-8244-c47250744428

📥 Commits

Reviewing files that changed from the base of the PR and between 4496269 and 9a36b5a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (16)
  • .claude/skills/write-component-jsdocs/SKILL.md
  • scripts/ts-api-extractor/src/config/schema.ts
  • scripts/ts-api-extractor/src/extract.ts
  • scripts/ts-api-extractor/src/translate/cache.ts
  • scripts/ts-api-extractor/src/translate/deepl.ts
  • scripts/ts-api-extractor/src/translate/llm-client.ts
  • scripts/ts-api-extractor/src/translate/llm-postprocess.ts
  • scripts/ts-api-extractor/src/translate/mqm-validator.ts
  • scripts/ts-api-extractor/src/translate/pipeline.ts
  • scripts/ts-api-extractor/src/translate/report.ts
  • scripts/ts-api-extractor/test/translate/cache.test.ts
  • scripts/ts-api-extractor/test/translate/deepl.test.ts
  • scripts/ts-api-extractor/test/translate/llm-postprocess.test.ts
  • scripts/ts-api-extractor/test/translate/mqm-validator.test.ts
  • scripts/ts-api-extractor/test/translate/pipeline.test.ts
  • scripts/ts-api-extractor/test/translate/report.test.ts
✅ Files skipped from review due to trivial changes (3)
  • .claude/skills/write-component-jsdocs/SKILL.md
  • scripts/ts-api-extractor/src/translate/cache.ts
  • scripts/ts-api-extractor/src/translate/report.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/ts-api-extractor/src/translate/deepl.ts
  • scripts/ts-api-extractor/test/translate/pipeline.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/ts-api-extractor/src/config/schema.ts (1)

47-94: ⚠️ Potential issue | 🟠 Major

validatePartialConfig should validate translation payload too.

translation was added to config shape, but runtime validation still skips it. Invalid values (e.g., non-boolean flags or unsupported locale) currently pass through and get merged.

🛠️ Proposed fix
 export function validatePartialConfig(config: PartialExtractorConfig): void {
@@
     if (config.verbose !== undefined && typeof config.verbose !== 'boolean') {
         throw new Error('Invalid verbose: expected boolean');
     }
+
+    if (config.translation !== undefined) {
+        if (typeof config.translation !== 'object' || config.translation === null) {
+            throw new Error('Invalid translation: expected object');
+        }
+        const t = config.translation;
+        if (t.enabled !== undefined && typeof t.enabled !== 'boolean') {
+            throw new Error('Invalid translation.enabled: expected boolean');
+        }
+        if (t.targetLocale !== undefined && t.targetLocale !== 'ko') {
+            throw new Error("Invalid translation.targetLocale: expected 'ko'");
+        }
+        if (t.llm !== undefined) {
+            if (typeof t.llm !== 'object' || t.llm === null) {
+                throw new Error('Invalid translation.llm: expected object');
+            }
+            if (t.llm.enabled !== undefined && typeof t.llm.enabled !== 'boolean') {
+                throw new Error('Invalid translation.llm.enabled: expected boolean');
+            }
+        }
+        if (t.validation !== undefined) {
+            if (typeof t.validation !== 'object' || t.validation === null) {
+                throw new Error('Invalid translation.validation: expected object');
+            }
+            if (t.validation.mqm !== undefined) {
+                if (typeof t.validation.mqm !== 'object' || t.validation.mqm === null) {
+                    throw new Error('Invalid translation.validation.mqm: expected object');
+                }
+                if (
+                    t.validation.mqm.enabled !== undefined &&
+                    typeof t.validation.mqm.enabled !== 'boolean'
+                ) {
+                    throw new Error('Invalid translation.validation.mqm.enabled: expected boolean');
+                }
+                if (
+                    t.validation.mqm.failOnError !== undefined &&
+                    typeof t.validation.mqm.failOnError !== 'boolean'
+                ) {
+                    throw new Error(
+                        'Invalid translation.validation.mqm.failOnError: expected boolean',
+                    );
+                }
+            }
+        }
+    }
 
     if (config.components !== undefined) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/src/config/schema.ts` around lines 47 - 94,
validatePartialConfig currently skips the new translation property; add
validation inside validatePartialConfig to ensure config.translation (when
defined) is an object (not null), validate any boolean flags on it using typeof
checks (e.g., translation.enabled, translation.optionalFlag) and validate locale
fields as strings or arrays of strings using the existing assertStringArray
helper; also verify locale values against the known supported locales list
(reference the project’s supported locales constant or enum) and throw
descriptive Errors (e.g., "Invalid translation: expected object", "Invalid
translation.enabled: expected boolean", "Invalid translation.locales:
unsupported locale") so invalid translation payloads are rejected before merge.
♻️ Duplicate comments (1)
scripts/ts-api-extractor/src/translate/report.ts (1)

40-40: ⚠️ Potential issue | 🟡 Minor

Sanitize component names before injecting into markdown headings.

c.name is currently rendered raw in a heading; special markdown characters can distort the report layout.

🛠️ Proposed fix
-    const lines = [`### ${c.name} — ${status}`];
+    const lines = [`### ${sanitizeMarkdown(c.name)} — ${status}`];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ts-api-extractor/src/translate/report.ts` at line 40, The heading
injection uses c.name raw (const lines = [`### ${c.name} — ${status}`]) which
can break Markdown; implement and call a sanitizer (e.g., escapeMarkdown or
markdownEscape) to escape Markdown-special characters in c.name before building
lines, or wrap the name in a safe code/span (e.g., inline code) so the generated
heading becomes `### ${escapeMarkdown(c.name)} — ${status}`; update the function
that builds the report lines to use this helper wherever c.name is interpolated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/ts-api-extractor/.env.example`:
- Around line 12-13: The environment variable entries are out-of-order for the
dotenv-linter rule: swap the two lines so LITELLM_API_KEY appears before
LITELLM_BASE_URL in the .env.example file; update the entries for the symbols
LITELLM_API_KEY and LITELLM_BASE_URL accordingly so the API key line comes
first.

In `@scripts/ts-api-extractor/src/translate/mqm-validator.ts`:
- Around line 73-85: The current guard only checks explanation and severity,
allowing malformed MqmError entries to slip through; update the validator that
returns MqmResult to perform a strict type guard over errors[] (the MqmError
shape) by verifying each entry has non-null object fields source_span and
mt_span (with their expected subfields), a category value matching the MqmError
category enum, and severity/explanation as strings; if any entry fails, log the
unexpected shape and return passResult() instead of casting, and ensure the
function that returns parsed as MqmResult uses this stronger predicate before
the cast.

In `@scripts/ts-api-extractor/src/translate/pipeline.ts`:
- Around line 220-232: The PASS/FAIL decision should use mqmResult.verdict
instead of mqmResult.errors.length; update the control in the mt-only branch to
check mqmResult.verdict (e.g., mqmResult.verdict === 'PASS') so a FAIL verdict
with an empty/partial errors array doesn't get treated as PASS, and ensure the
returned object fields (pipeline, hadErrors, hadOverEdit) are set consistently
when verdict !== 'PASS' (and continue to populate mqmErrorsByEntryIdx and
allowedEditSpans only for non-PASS cases using mqmResult.errors).

---

Outside diff comments:
In `@scripts/ts-api-extractor/src/config/schema.ts`:
- Around line 47-94: validatePartialConfig currently skips the new translation
property; add validation inside validatePartialConfig to ensure
config.translation (when defined) is an object (not null), validate any boolean
flags on it using typeof checks (e.g., translation.enabled,
translation.optionalFlag) and validate locale fields as strings or arrays of
strings using the existing assertStringArray helper; also verify locale values
against the known supported locales list (reference the project’s supported
locales constant or enum) and throw descriptive Errors (e.g., "Invalid
translation: expected object", "Invalid translation.enabled: expected boolean",
"Invalid translation.locales: unsupported locale") so invalid translation
payloads are rejected before merge.

---

Duplicate comments:
In `@scripts/ts-api-extractor/src/translate/report.ts`:
- Line 40: The heading injection uses c.name raw (const lines = [`### ${c.name}
— ${status}`]) which can break Markdown; implement and call a sanitizer (e.g.,
escapeMarkdown or markdownEscape) to escape Markdown-special characters in
c.name before building lines, or wrap the name in a safe code/span (e.g., inline
code) so the generated heading becomes `### ${escapeMarkdown(c.name)} —
${status}`; update the function that builds the report lines to use this helper
wherever c.name is interpolated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 778f6632-b235-4958-a327-90f61210067c

📥 Commits

Reviewing files that changed from the base of the PR and between 9a36b5a and 94840b7.

📒 Files selected for processing (13)
  • scripts/ts-api-extractor/.env.example
  • scripts/ts-api-extractor/src/config/schema.ts
  • scripts/ts-api-extractor/src/translate/cache.ts
  • scripts/ts-api-extractor/src/translate/llm-postprocess.ts
  • scripts/ts-api-extractor/src/translate/mqm-validator.ts
  • scripts/ts-api-extractor/src/translate/pipeline.ts
  • scripts/ts-api-extractor/src/translate/report.ts
  • scripts/ts-api-extractor/src/translate/types.ts
  • scripts/ts-api-extractor/test/translate/cache.test.ts
  • scripts/ts-api-extractor/test/translate/llm-postprocess.test.ts
  • scripts/ts-api-extractor/test/translate/mqm-validator.test.ts
  • scripts/ts-api-extractor/test/translate/pipeline.test.ts
  • scripts/ts-api-extractor/test/translate/report.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • scripts/ts-api-extractor/src/translate/llm-postprocess.ts
  • scripts/ts-api-extractor/test/translate/cache.test.ts
  • scripts/ts-api-extractor/test/translate/report.test.ts
  • scripts/ts-api-extractor/src/translate/cache.ts
  • scripts/ts-api-extractor/test/translate/mqm-validator.test.ts

Comment thread scripts/ts-api-extractor/.env.example Outdated
Comment thread scripts/ts-api-extractor/src/translate/mqm-validator.ts Outdated
Comment thread scripts/ts-api-extractor/src/translate/pipeline.ts Outdated
Comment thread apps/website/.gitignore

# generated content
.contentlayer
**/.i18n-report.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Markdown file that generates a report once verification is complete. As it changes every time it is run and is intended for developers’ reference only, I have marked it as ignore

@MaxLee-dev

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
scripts/translation-pipeline/src/cli/run.ts (1)

84-108: ⚡ Quick win

Read the input set once and keep file identity attached.

This currently parses the same directory twice and later zips rawDocs to result.props by position. That stays correct only while both scans and the pipeline output preserve identical order; once that assumption breaks, the CLI can write the wrong translation into the wrong file without noticing. Returning { fileName, raw, doc } from a single reader would remove both the duplicate I/O and the positional coupling.

Also applies to: 151-160, 217-224

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/translation-pipeline/src/cli/run.ts` around lines 84 - 108, The
reader should scan and parse the input directory only once and return per-file
records that keep file identity together (instead of returning just
TranslatableDoc[]), so modify readEnJsonFiles to return an array of objects like
{ fileName, raw, doc } produced by calling normalizeDoc(raw, fileName)
immediately after parsing; update any downstream uses that currently rely on
rawDocs and result.props zipping by index (references: readEnJsonFiles,
normalizeDoc, rawDocs, result.props) to consume the preserved fileName on each
record when writing outputs, and remove the duplicate directory re-reads
elsewhere (also update the other reader usages noted around the alternate code
paths) so ordering no longer must be relied upon.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/translation-pipeline/src/cli/index.ts`:
- Around line 1-9: Currently the try/catch around
process.loadEnvFile(path.resolve(process.cwd(), '.env')) swallows all errors;
change this to first check for file existence (use fs.existsSync or fs/promises
access) on the resolved '.env' path and only call process.loadEnvFile when the
file exists so that parse/read errors from process.loadEnvFile (not missing-file
cases) are allowed to surface; keep the same path.resolve(process.cwd(), '.env')
resolution and remove the broad try/catch so real failures in
process.loadEnvFile are not masked.

In `@scripts/translation-pipeline/src/translator/translator.ts`:
- Around line 66-69: The lookup for translated text currently falls back to
outcomes.get(unit.id), which can leak translations across components; change the
resolution in the loop over units to remove the outcomes.get(unit.id) fallback
so you only use outcomes.get(getTranslationUnitKey(unit)) and default to
unit.source if that returns undefined (i.e., update the translated assignment in
translator.ts to drop the unit.id lookup and rely solely on
getTranslationUnitKey(unit) plus unit.source).

In `@scripts/translation-pipeline/tsconfig.json`:
- Line 7: The tsconfig's compilerOptions.types currently lists only
"vitest/globals" which prevents Node API typings from being available; update
the "types" array in tsconfig.json (the "types" entry shown) to include "node"
alongside "vitest/globals" so Node globals and APIs (fs, path, os, crypto,
process) are correctly type-checked.

---

Nitpick comments:
In `@scripts/translation-pipeline/src/cli/run.ts`:
- Around line 84-108: The reader should scan and parse the input directory only
once and return per-file records that keep file identity together (instead of
returning just TranslatableDoc[]), so modify readEnJsonFiles to return an array
of objects like { fileName, raw, doc } produced by calling normalizeDoc(raw,
fileName) immediately after parsing; update any downstream uses that currently
rely on rawDocs and result.props zipping by index (references: readEnJsonFiles,
normalizeDoc, rawDocs, result.props) to consume the preserved fileName on each
record when writing outputs, and remove the duplicate directory re-reads
elsewhere (also update the other reader usages noted around the alternate code
paths) so ordering no longer must be relied upon.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b00616f8-db86-475c-8cdc-842b3c7fe6d1

📥 Commits

Reviewing files that changed from the base of the PR and between 6d8ede1 and fe55e6e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (33)
  • apps/website/.env.example
  • apps/website/docs-extractor.config.mjs
  • apps/website/package.json
  • pnpm-workspace.yaml
  • scripts/translation-pipeline/eslint.config.mjs
  • scripts/translation-pipeline/package.json
  • scripts/translation-pipeline/src/cache/cache.test.ts
  • scripts/translation-pipeline/src/cache/cache.ts
  • scripts/translation-pipeline/src/cli/index.ts
  • scripts/translation-pipeline/src/cli/run.test.ts
  • scripts/translation-pipeline/src/cli/run.ts
  • scripts/translation-pipeline/src/defaults.ts
  • scripts/translation-pipeline/src/postprocess/postprocess.test.ts
  • scripts/translation-pipeline/src/postprocess/postprocess.ts
  • scripts/translation-pipeline/src/report/report.test.ts
  • scripts/translation-pipeline/src/report/report.ts
  • scripts/translation-pipeline/src/translation/client.test.ts
  • scripts/translation-pipeline/src/translation/client.ts
  • scripts/translation-pipeline/src/translation/json.ts
  • scripts/translation-pipeline/src/translation/translate.test.ts
  • scripts/translation-pipeline/src/translation/translate.ts
  • scripts/translation-pipeline/src/translator/batch-lifecycle.ts
  • scripts/translation-pipeline/src/translator/translator.test.ts
  • scripts/translation-pipeline/src/translator/translator.ts
  • scripts/translation-pipeline/src/types.ts
  • scripts/translation-pipeline/src/validation/validator.test.ts
  • scripts/translation-pipeline/src/validation/validator.ts
  • scripts/translation-pipeline/tests/cli.test.ts
  • scripts/translation-pipeline/tsconfig.json
  • scripts/translation-pipeline/tsup.config.ts
  • scripts/translation-pipeline/vitest.config.ts
  • scripts/ts-api-extractor/package.json
  • scripts/ts-api-extractor/src/extract.ts
💤 Files with no reviewable changes (1)
  • apps/website/docs-extractor.config.mjs
✅ Files skipped from review due to trivial changes (8)
  • scripts/translation-pipeline/src/defaults.ts
  • scripts/translation-pipeline/tsup.config.ts
  • scripts/translation-pipeline/vitest.config.ts
  • scripts/translation-pipeline/eslint.config.mjs
  • scripts/translation-pipeline/package.json
  • scripts/translation-pipeline/src/cache/cache.ts
  • pnpm-workspace.yaml
  • scripts/ts-api-extractor/package.json

Comment thread scripts/translation-pipeline/src/cli/index.ts
Comment thread scripts/translation-pipeline/src/translator/translator.ts Outdated
Comment thread scripts/translation-pipeline/tsconfig.json Outdated
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@MaxLee-dev

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Comment thread apps/website/package.json
"extract": "ts-api-extractor",
"format": "prettier --write \"./src/**/*.{ts,tsx,md}\"",
"format:check": "prettier --check \"./src/**/*.{ts,tsx,md}\"",
"i18n": "pnpm extract && pnpm translate",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NIT]

How about naming that command so that it’s clearly associated with “translate”? The “i18n” command and “translate” are related, but just by looking at the names, it might not be easy to tell they’re connected...!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/website/package.json (1)

1-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

웹사이트 패키지에 Node/PNPM 버전 제약을 명시해 주세요.

현재 apps/website/package.jsonengines.nodepackageManager가 없어 환경 드리프트가 발생할 수 있습니다.

제안 수정안
 {
     "name": "website",
     "version": "1.0.0",
     "private": true,
     "type": "module",
+    "packageManager": "pnpm@10.5.1",
+    "engines": {
+        "node": ">=20.19"
+    },
     "scripts": {

As per coding guidelines, apps/website/**/{.nvmrc,.node-version,package.json}는 Node.js v20.19+를 요구하고, apps/website/**/{pnpm-lock.yaml,package.json}는 PNPM v10.5.1+ 사용을 요구합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/package.json` around lines 1 - 17, The apps/website/package.json
file is missing engine version constraints that enforce Node.js and PNPM
requirements, which can lead to environment drift. Add an `engines` field to
specify the minimum Node.js version requirement (v20.19+) and a `packageManager`
field to specify the required PNPM version (v10.5.1+) at the root level of the
package.json object. These fields will ensure all developers and CI environments
use compatible versions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/translation-pipeline/CLAUDE.md`:
- Around line 33-41: The code fence in the architecture diagram block lacks a
language specification, which triggers a markdownlint warning. Add the language
identifier `text` to the opening code fence delimiter so it reads ```text
instead of just ```. This applies to the code block starting with the cli/run.ts
architecture diagram showing the translation pipeline flow.

In `@scripts/translation-pipeline/README.md`:
- Around line 7-15: The code fence blocks in the README.md file are missing
language identifiers, which triggers markdownlint warnings. Add the language
identifier `text` to both code fences: the first one containing the pipeline
flow diagram showing packages/core components through .i18n-report.md, and the
second one showing the directory structure with cli/, translator/, translation/,
validation/, postprocess/, cache/, report/, and the configuration files
(defaults.ts and types.ts). Update the opening triple backticks from just ``` to
```text for both blocks.

In `@scripts/ts-api-extractor/package.json`:
- Around line 38-43: The `@vitest/coverage-v8` dependency is pinned to `^2.1.9`
while `vitest` has been upgraded to `^3.0.0`, creating a peer dependency
mismatch that will cause the test:coverage workflow to fail. Update the version
of `@vitest/coverage-v8` from `^2.1.9` to `^3.0.0` to align with the `vitest`
major version and ensure compatibility.

---

Outside diff comments:
In `@apps/website/package.json`:
- Around line 1-17: The apps/website/package.json file is missing engine version
constraints that enforce Node.js and PNPM requirements, which can lead to
environment drift. Add an `engines` field to specify the minimum Node.js version
requirement (v20.19+) and a `packageManager` field to specify the required PNPM
version (v10.5.1+) at the root level of the package.json object. These fields
will ensure all developers and CI environments use compatible versions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca9241f5-f753-41a6-af28-3223a1677f0e

📥 Commits

Reviewing files that changed from the base of the PR and between ec4a6d0 and b96d2f4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/website/package.json
  • handoff.md
  • pnpm-workspace.yaml
  • scripts/translation-pipeline/CLAUDE.md
  • scripts/translation-pipeline/README.md
  • scripts/translation-pipeline/package.json
  • scripts/ts-api-extractor/package.json
💤 Files with no reviewable changes (1)
  • scripts/translation-pipeline/package.json
✅ Files skipped from review due to trivial changes (1)
  • handoff.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • pnpm-workspace.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/website/package.json (1)

1-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

웹사이트 패키지에 Node/PNPM 버전 제약을 명시해 주세요.

현재 apps/website/package.jsonengines.nodepackageManager가 없어 환경 드리프트가 발생할 수 있습니다.

제안 수정안
 {
     "name": "website",
     "version": "1.0.0",
     "private": true,
     "type": "module",
+    "packageManager": "pnpm@10.5.1",
+    "engines": {
+        "node": ">=20.19"
+    },
     "scripts": {

As per coding guidelines, apps/website/**/{.nvmrc,.node-version,package.json}는 Node.js v20.19+를 요구하고, apps/website/**/{pnpm-lock.yaml,package.json}는 PNPM v10.5.1+ 사용을 요구합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/package.json` around lines 1 - 17, The apps/website/package.json
file is missing engine version constraints that enforce Node.js and PNPM
requirements, which can lead to environment drift. Add an `engines` field to
specify the minimum Node.js version requirement (v20.19+) and a `packageManager`
field to specify the required PNPM version (v10.5.1+) at the root level of the
package.json object. These fields will ensure all developers and CI environments
use compatible versions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/translation-pipeline/CLAUDE.md`:
- Around line 33-41: The code fence in the architecture diagram block lacks a
language specification, which triggers a markdownlint warning. Add the language
identifier `text` to the opening code fence delimiter so it reads ```text
instead of just ```. This applies to the code block starting with the cli/run.ts
architecture diagram showing the translation pipeline flow.

In `@scripts/translation-pipeline/README.md`:
- Around line 7-15: The code fence blocks in the README.md file are missing
language identifiers, which triggers markdownlint warnings. Add the language
identifier `text` to both code fences: the first one containing the pipeline
flow diagram showing packages/core components through .i18n-report.md, and the
second one showing the directory structure with cli/, translator/, translation/,
validation/, postprocess/, cache/, report/, and the configuration files
(defaults.ts and types.ts). Update the opening triple backticks from just ``` to
```text for both blocks.

In `@scripts/ts-api-extractor/package.json`:
- Around line 38-43: The `@vitest/coverage-v8` dependency is pinned to `^2.1.9`
while `vitest` has been upgraded to `^3.0.0`, creating a peer dependency
mismatch that will cause the test:coverage workflow to fail. Update the version
of `@vitest/coverage-v8` from `^2.1.9` to `^3.0.0` to align with the `vitest`
major version and ensure compatibility.

---

Outside diff comments:
In `@apps/website/package.json`:
- Around line 1-17: The apps/website/package.json file is missing engine version
constraints that enforce Node.js and PNPM requirements, which can lead to
environment drift. Add an `engines` field to specify the minimum Node.js version
requirement (v20.19+) and a `packageManager` field to specify the required PNPM
version (v10.5.1+) at the root level of the package.json object. These fields
will ensure all developers and CI environments use compatible versions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca9241f5-f753-41a6-af28-3223a1677f0e

📥 Commits

Reviewing files that changed from the base of the PR and between ec4a6d0 and b96d2f4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/website/package.json
  • handoff.md
  • pnpm-workspace.yaml
  • scripts/translation-pipeline/CLAUDE.md
  • scripts/translation-pipeline/README.md
  • scripts/translation-pipeline/package.json
  • scripts/ts-api-extractor/package.json
💤 Files with no reviewable changes (1)
  • scripts/translation-pipeline/package.json
✅ Files skipped from review due to trivial changes (1)
  • handoff.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • pnpm-workspace.yaml
🛑 Comments failed to post (3)
scripts/translation-pipeline/CLAUDE.md (1)

33-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

코드 펜스에 언어를 지정하세요.

이 아키텍처 다이어그램 블록은 언어가 없어 markdownlint 경고가 납니다. text를 붙여 주세요.

수정 예시
-```
+```text
 cli/run.ts
   → translator/translator.ts          # cache lookup, initial translation, outcome merge
       → translation/translate.ts      # LLM initial translation (batch of 20)
       → translator/batch-lifecycle.ts # MQM → postprocess → final MQM
           → validation/validator.ts   # batch MQM evaluation (batch of 10)
           → postprocess/postprocess.ts # corrective rewrite on MQM FAIL
   → report/report.ts                  # renders .i18n-report.md
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 33-33: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/translation-pipeline/CLAUDE.md` around lines 33 - 41, The code fence
in the architecture diagram block lacks a language specification, which triggers
a markdownlint warning. Add the language identifier `text` to the opening code
fence delimiter so it reads ```text instead of just ```. This applies to the
code block starting with the cli/run.ts architecture diagram showing the
translation pipeline flow.

Source: Linters/SAST tools

scripts/translation-pipeline/README.md (1)

7-15: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

코드 펜스에 언어를 지정하세요.

두 텍스트 블록 모두 언어가 없어 markdownlint 경고가 납니다. text를 붙여 주세요.

수정 예시
-```
+```text
 packages/core components
     ↓ ts-api-extractor
 generated/en/*.json
     ↓ translation-pipeline
 generated/ko/*.json
 .translation-cache.json
 .i18n-report.md
-```
+```
...
-```
+```text
 src/
 ├── cli/           # Entry point, argument parsing, file I/O
 ├── translator/    # Unit collection, cache lookup, batch lifecycle
 ├── translation/   # LiteLLM calls, initial translation prompt
 ├── validation/    # MQM evaluation prompt and response validation
 ├── postprocess/   # MQM FAIL feedback-based translation correction
 ├── cache/         # SHA256 key generation, cache load/save
 ├── report/        # Per-component stats, report rendering
 ├── defaults.ts    # Default model configuration
 └── types.ts       # Shared type definitions
-```
+```

Also applies to: 86-97

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/translation-pipeline/README.md` around lines 7 - 15, The code fence
blocks in the README.md file are missing language identifiers, which triggers
markdownlint warnings. Add the language identifier `text` to both code fences:
the first one containing the pipeline flow diagram showing packages/core
components through .i18n-report.md, and the second one showing the directory
structure with cli/, translator/, translation/, validation/, postprocess/,
cache/, report/, and the configuration files (defaults.ts and types.ts). Update
the opening triple backticks from just ``` to ```text for both blocks.

Source: Linters/SAST tools

scripts/ts-api-extractor/package.json (1)

38-43: ⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Vitest/Coverage provider compatibility from npm metadata
npm view vitest@3.0.0 version peerDependencies
npm view `@vitest/coverage-v8`@2.1.9 version peerDependencies
npm view `@vitest/coverage-v8`@3.0.0 version peerDependencies

Repository: goorm-dev/vapor-ui

Length of output: 439


Update @vitest/coverage-v8 to match vitest major version

Line 43 upgrades vitest to ^3.0.0, but Line 38 keeps @vitest/coverage-v8 at ^2.1.9. According to npm peer dependencies, @vitest/coverage-v8@2.1.9 requires vitest@2.1.9, while @vitest/coverage-v8@3.0.0 requires vitest@3.0.0. This mismatch will cause failures in the test:coverage workflow.

Suggested fix
-        "`@vitest/coverage-v8`": "^2.1.9",
+        "`@vitest/coverage-v8`": "^3.0.0",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        "`@vitest/coverage-v8`": "^3.0.0",
        "eslint": "^9.39.4",
        "tsup": "^8.5.1",
        "tsx": "^4.21.0",
        "typescript": "catalog:",
        "vitest": "^3.0.0"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/ts-api-extractor/package.json` around lines 38 - 43, The
`@vitest/coverage-v8` dependency is pinned to `^2.1.9` while `vitest` has been
upgraded to `^3.0.0`, creating a peer dependency mismatch that will cause the
test:coverage workflow to fail. Update the version of `@vitest/coverage-v8` from
`^2.1.9` to `^3.0.0` to align with the `vitest` major version and ensure
compatibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants