feat: v2 translations manager with fallback chains#1294
Open
stevenle wants to merge 10 commits into
Open
Conversation
Adds an `i18n.fallbacks` config option for declaring locale fallback chains for translations (e.g. en-CA -> en-GB -> en), along with a shared resolveLocaleFallbacks() helper (server + browser safe) and unit tests. Also surfaces i18n.defaultLocale/fallbacks and the v2TranslationsManager experiment flag on the CMS UI's window.__ROOT_CTX types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
- Fix the inverted experiments.v2TranslationsManager check in
getTranslationsManager() and add isV2TranslationsEnabled().
- TranslationsManager.loadTranslations(): use array-contains-any for tag
filters, chunk id `in` queries (10/chunk, parallel), and merge results
in ids order for deterministic precedence (doc-specific last/wins).
- saveTranslations(): add {linkedSheet, modifiedBy} options, chunk batch
commits at 400 ops, and remove the misleading inner update loop.
- Add addPublishTranslationsOps(), publishTranslationsBulk(),
getTranslationsLocaleDocs() and a public translationsForLocaleV2()
fallback-chain resolver; loadTranslationsForLocale() now defaults its
fallback chain from i18n.fallbacks.
- publishDocs()/publishScheduledDocs() (and releases via publishDocs)
publish each doc's draft translations in the same write batch as the
doc; unpublishDocs() deletes the published locale docs and clears the
draft docs' published metadata. All flag-gated.
- Rewrite BatchRequest/BatchResponse for the per-locale schema: fix the
translations double-fetch, auto-collect translations ids from query
results when translate is enabled, fetch exact locale-doc refs expanded
through i18n.fallbacks (chunked getAll) with an id-query fallback when
locales are unknown, and resolve fallback chains in getTranslations().
- Remove the orphaned incompatible TranslationsDoc schema and the unused
dbTranslationsPath()/dbTranslationsRef() helpers.
- Add Firestore-emulator tests for the manager, publish integration, and
BatchRequest translation resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
Adds migrateV1TranslationsIfNeeded(), which copies v1 translations into
per-locale v2 docs and publishes them (v1 saves were immediately live),
leaving the v1 data untouched as a backup. Migration state is tracked in
a Projects/{p}/TranslationsManager/migration doc: after the first
successful run each boot costs a single read, a Firestore transaction
guards against concurrent runs, and a stale running lock expires after
10 minutes.
The CMS plugin (gated on experiments.v2TranslationsManager) awaits the
migration in the preBuild hook — failing the build with an ADC
credentials hint — and fire-and-forgets it on dev server startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
When experiments.v2TranslationsManager is enabled, createRoute's SSR handler and SSG props load translations from the v2 TranslationsManager (using the same ids as the v1 tags, honoring the draft/published mode from ?preview=true) and flatten them per locale through the i18n.fallbacks chain. The missing-translations check reads the doc's draft v2 translations the same way. Flag off keeps v1 reads unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
Adds ui/utils/translations-manager.ts (Firebase Web SDK, mirroring the core per-locale doc paths): list/load/save/publish/unpublish/delete for translations docs, batch-op helpers shared with doc publishing, and a testV2TranslationsEnabled() flag helper. Locale docs are grouped by the doc data's id field (never parsed from the ':' doc key, which is ambiguous with '--' slugs), and per-id modified/published timestamps drive a hasUnpublishedChanges status. When the flag is enabled, cmsPublishDocs() publishes each doc's draft translations in the same write batch as the doc (now with op-counted chunked commits, <=450 ops/batch, each doc + its translations kept in one chunk, and upfront lock/archive validation so chunked commits can't partially publish); the releases UI inherits this. cmsUnpublishDoc() clears the doc's published translations and cmsDeleteDoc() removes the doc's translations entirely. PublishDocModal notes that translations publish together with the doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
Adds a TranslationsManagerPage list view (left tree grouping ids by
their prefix, with non-prefixed ids like 'common' under a Global group;
table with locale count, status badge, modified/published timestamps,
search, and an Edit/Publish/Delete row menu) and a
TranslationsManagerEditPage editor (source x translation-language grid
whose rows are the union of the doc's extracted strings and the stored
draft strings — stored-only rows badged 'unused' — with an explicit
Save, a confirm-modal Publish, an 'Add source string' input for non-doc
ids, and a draft/published status badge).
When experiments.v2TranslationsManager is on, /cms/translations routes
to the manager and /cms/translations/:translationsId* (wildcard, so
existing {collection}/{slug} deep links keep working) routes to the
editor; flag off keeps the four v1 translations routes. Conditional
route children are arrays (preact-iso's Router only flattens arrays).
The DocEditor Localization button navigates to the manager editor when
the flag is on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
Adds a minor changeset for @blinkk/root and @blinkk/root-cms (noting the TranslationsDoc export removal), runs the admin-SDK emulator tests against a separate emulator project id so security.test.ts's clearFirestore() can't race them, and applies lint formatting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE
ffd8040 to
dd713c0
Compare
Member
Author
|
@jeremydw here's the cms ui for the new translations manager, let me know if you want to review it at all or if i should go ahead and submit. it's gated behind an "experiments" flag. |
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Implements a complete v2 translations manager system with locale fallback support, replacing the v1 translations system. This feature is gated behind the
experiments.v2TranslationsManagerflag.Summary
This PR introduces a new translations management system that stores translations in per-locale Firestore documents with support for locale fallback chains. The system includes:
core/translations-manager.ts): Handles saving, loading, publishing, and unpublishing translations with per-locale document storagecore/translations-migration.ts): Auto-migrates existing v1 translations on dev server startup and before production buildsshared/locale-fallbacks.ts): Implements configurable fallback chains (e.g.,en-CA→en-GB→en)Key Changes
Projects/{p}/TranslationsManager/{draft|published}/Translations/{id}:{locale}, replacing the flat v1 structurei18n.fallbacksconfig allows defining fallback locales (e.g., regional variants falling back to base language)inqueries into 10-item chunks to respect Firestore limitsui/utils/translations-manager.tsmirrors the Firestore paths and operations for the UIImplementation Details
https://claude.ai/code/session_01Pw4Wrj1oL9DuVbKoXasBTE