feat(translation): glossary-based tag translation + Lexical image caption translation - #2816
Conversation
…aption translation Tags now translate through translation entries (post.tag dict keyPath) instead of per-article AI translation; the source tag stays in data.tags and translated names ride in meta.glossary.tags. Image/gallery captions and alt text join the Lexical translation walk and block fingerprints. Closes #2815 Claude-Session: https://claude.ai/code/session_01EMxdDyw65qUBDpRERXqp3y
SafeDep Report SummaryNo dependency changes detected. Nothing to scan. This report is generated by SafeDep GitHub App |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 649301e181
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (initial.tags?.length) { | ||
| fullTranslations[META_TAGS_KEY] = encodeTags(initial.tags) | ||
| } | ||
| for (const paragraph of paragraphs) { |
There was a problem hiding this comment.
Remove tags from the Markdown writer prompt
When translating a Markdown post with tags, deleting the reviewer unit here does not stop per-article tag translation: the writer call still passes tags: content.tags to AI_PROMPTS.translationStream, whose prompt includes a TAGS block and requires translated tag output. Markdown translations therefore still spend tokens translating tags and persist those per-article results—including through the raw translation endpoints—even though delivery has moved tags to the glossary; omit tags from the writer input/output contract as well.
Useful? React with 👍 / 👎.
| lookupKey: TranslationEntryService.hashSourceText(tag), | ||
| sourceText: tag, |
There was a problem hiding this comment.
Preserve distinct source tags in glossary lookups
When posts contain distinct tags that differ only by case or surrounding whitespace, such as React and react, hashSourceText gives them the same lookup key here. The database uniqueness constraint and the serving-time hashToText map then collapse the two entries, so a tag-list response containing both exact, case-sensitive route names can emit a glossary pair for only one of them. Use an exact-source lookup key for post.tag, or retain every source alias when resolving a normalized key.
Useful? React with 👍 / 👎.



Summary
Fixes #2815.
post.tagdict keyPath in translation entries. Auto-collected onPOST_CREATE/UPDATE/REPUBLISH(gated byenableAutoGenerateTranslation), full backfill through the existing admin "generate entries" action. Per-article AI translation no longer translates tags;data.tagsalways carries the source names.meta.glossary.tags: [{ source, translated }]on post list/detail, category tag list, category detail (tagsSum+ children) and the?tag=trueroute. Pairs instead of a keyed map so free-text tag names survive the response case transform.api-clientbase meta type updated.image(caption,altText) andgallery(images[].alt) joinCOMPLEX_NODE_EXTRACTORSin the Lexical translation parser, and block fingerprints include them so caption edits mark the block stale.Design:
docs/superpowers/specs/2026-09-02-tag-glossary-and-image-caption-translation-design.md.Not wired:
/aggregatetitled items and/search(no consumer renders tags there).ai_translations.tagscolumn is kept, now holding source tags; can be dropped in a later contract migration.Test plan
lexical-translation-parser.spec: image/gallery segments, round-trip restorehelper.lexical.service.spec: caption/alt change fingerprinttranslation-entry.service.spec, newai-translation-event-handler.service.spec,helper.translation.service.spec(buildTagGlossary, tags untouched by article translation)tsc --noEmit, eslint on changed filesmeta.glossary.tagshttps://claude.ai/code/session_01EMxdDyw65qUBDpRERXqp3y