test(vue): add Vite SSR example - #2033
Open
eoinest wants to merge 3 commits into
Open
Conversation
Contributor
size-limit report 📦
|
eoinest
changed the base branch from
e/vue/fix-formatter-hydration
to
e/vue/add-vue-vite-example
August 6, 2026 00:13
This was referenced Aug 5, 2026
Contributor
Author
1 similar comment
Contributor
Author
Contributor
Author
1 similar comment
Contributor
Author
Contributor
Author
eoinest
force-pushed
the
e/vue/add-spa-ssr-examples
branch
from
August 6, 2026 22:29
c5a5fb7 to
dde1fee
Compare
Contributor
Author
eoinest
force-pushed
the
e/vue/add-spa-ssr-examples
branch
from
August 7, 2026 00:43
dde1fee to
714a960
Compare
Contributor
Author
eoinest
added a commit
that referenced
this pull request
Aug 7, 2026
## Summary - Add an unstable, lightweight `gt-vue` runtime with `T`, child-only `Var`, typed-value `Num` / `DateTime` / `Currency`, `Plural`, `Branch`, `useGT`, `useMessages`, and `msg`. - Keep string translation deliberately narrow: `$context` is the only public metadata option; all content is literal STRING data with no ICU interpolation, formatting fields, or development hot reload. - Provide isolated `createGT()` instances, async per-locale catalog caching, reactive locale changes, latest-wins concurrency, retryable loader diagnostics, and source-locale fast paths for client and SSR use. - Preserve Vue component props/listeners and scoped slots, keep rich-content IDs and plural selection compatible with react-core, exclude presentation/runtime attributes from persisted Branch hashes, and suppress unsupported fallthrough attrs on wrapperless value components. - Share the literal STRING hash/registration contract through `gt-i18n/internal/string`; `gt-i18n` is a real external runtime dependency in both ESM and CJS. ## Testing - `gt-vue`: full runtime suite passed, including SSR/hydration, keyed VNode reconciliation, opaque slots, Suspense, and concurrency - `gt-i18n`: 33 files / 314 tests - `@generaltranslation/react-core`: 12 files / 50 tests, including shared hand-pinned wire-format fixtures - Forced Turbo build/typecheck for the runtime dependency graph — passed - Root library-default, lint, and format gates — passed - Size limits — passed (`gt-vue` 7.56 kB Brotli; `gt-i18n/internal/string` 2.97 kB) - Packed consumers on Vue 3.3.13 and 3.5.40 — passed ESM, CJS, SSR, `vue-tsc`, Vite production builds, and real Chromium locale/formatting loops with zero console warnings/errors - Changesets simulation — `gt-vue@0.1.0` correctly depends on `gt-i18n@1.1.0` ## Notes - `gt-vue` is explicitly documented as unstable and independently versioned during 0.x. - Changesets: minor for both the first `gt-vue` release and the literal STRING contract update in `gt-i18n`. - Stack 1/4. Base: `main`. Next: #2013, #2014, then #2033. - This PR is not being merged or published yet. <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR introduces `gt-vue`, a new lightweight Vue 3 i18n runtime package, plus a shared `gt-i18n/internal/string` contract for STRING-format translation hashing and cookie utilities consumed by both Vue and React runtimes. It also refactors `gt-i18n`'s `msg` to delegate to a new `msgString` path when `$format: 'STRING'` is requested, and consolidates cookie-name constants from `react-core` into `gt-i18n`. - **`gt-vue` runtime**: adds `T`, `Var`, `Num`, `DateTime`, `Currency`, `Plural`, `Branch`, `createGT`, `useGT`, `useMessages`, `useLocale`, `useSetLocale`, and `msg` — with reactive locale state, per-locale catalog caching, latest-wins concurrency, SSR/hydration support, and source-locale fast paths. - **`gt-i18n` additions**: `msgString`, `encodeMsg`, `hashStringMessage`, browser cookie utilities, and new internal entry points (`internal-string`, `internal-cookies`) shared across frameworks. - **`react-core` cleanup**: `cookieNames.ts` now re-exports from `gt-i18n/internal/cookies` rather than duplicating constants. <details open><summary><h3>Confidence Score: 5/5</h3></summary> - This PR is safe to review and merge — no functional regressions or data-loss paths were found across the new Vue runtime, the shared gt-i18n STRING contract, or the react-core consolidation. - The new gt-vue package adds a large surface area (rich-content rendering, locale concurrency, SSR/hydration, cookie management) but each subsystem is self-contained and backed by targeted tests. The latest-wins concurrency logic in setLocale, the revision-counter reactivity contract, the source-locale fast-path that deliberately skips catalog reads, the VNode identity cache rollback on render errors, and the `isEncodedTranslationOptions` type guard before msg decode are all implemented correctly. The react-core cookie constant consolidation is a straight re-export with no behavioral change. No blocking issues were found. - No files require special attention. </details> <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | packages/vue/src/runtime/state.ts | Core plugin factory. Locale concurrency (latest-wins), per-locale catalog caching, cookie-backed locale persistence, and reactive revision counter are all implemented correctly. `console.error` on load failure is intentional diagnostic output, not debug logging. | | packages/vue/src/rendering/translateVueChildren.ts | Rich-content translation engine. Source serialization, hash-based catalog lookup, variable/branch/plural rendering, VNode identity caching with rollback, and metadata copying all look correct. `copyRenderMetadata` relies on undocumented `ctx` / `slotScopeIds` fields (flagged in a previous review thread). `cloneWithProps` for components intentionally passes a VNode to `h()` to take the clone-and-renormalize path. | | packages/vue/src/runtime/localeCookie.ts | Cookie-backed locale accessor. SSR/hydration contract is correct: explicit locale overrides a stale browser cookie on first mount, `getLocale` reads live cookie on browser, `serverLocale` closure isolates per-request SSR state. | | packages/vue/src/composables/strings.ts | useGT and useMessages composables. Encoded msg metadata is decoded and short-circuited through the precomputed hash path; raw strings fall through to the regular STRING hash lookup. Nullish passthrough is correct. | | packages/vue/src/components/variables.ts | Num, DateTime, Currency, and Var components. Null/empty-string guards, NaN fallback to String(value), and _locale override for the rich-translation pipeline are all correctly implemented. | | packages/vue/src/components/branches.ts | Plural and Branch components. getBranchNames deduplicates attr/slot names via Set (regression test included). getBranchContent slot-wins logic and getFormatLocales fallback are correct. | | packages/i18n/src/translation-functions/msg/msgString.ts | New STRING-format msg registration path. Array recursion with $id indexing, STRING format tagging, and encodeMsg delegation are all correct. The early return when options is absent preserves the original input's type. | | packages/i18n/src/utils/hashStringMessage.ts | Canonical STRING catalog key computation. Precomputed $_hash short-circuit is used correctly by both msgString and translateString to avoid re-hashing. | | packages/react-core/src/setup/cookieNames.ts | Replaced duplicated cookie constants with re-exports from gt-i18n/internal/cookies. Single source of truth for cookie names across runtimes. | | packages/vue/src/components/utils.ts | Shared component utilities. isBranchAttribute correctly excludes event handlers, ARIA/data attributes, and the NON_BRANCH_ATTRIBUTE_NAMES set. getBranchNames deduplication and getBranchContent slot-priority logic look correct. | </details> <details><summary><h3>Sequence Diagram</h3></summary> ```mermaid sequenceDiagram participant App as Vue App participant Plugin as GTPlugin (createGT) participant State as GTState participant Cookie as CookieBackedLocale participant Loader as loadTranslations participant Catalog as catalogs Map participant T as T Component participant Renderer as translateVueChildren App->>Plugin: app.use(plugin) Plugin->>State: provide(gtContextKey, state) Plugin->>Loader: load(getLocale()) [async, fire-and-forget] Loader-->>Catalog: catalogs.set(locale, catalog) Loader-->>State: "revision.value += 1" App->>Plugin: setLocale('fr') Plugin->>Loader: load('fr') note over Plugin,Loader: concurrent requests share one Promise<br/>latest-wins: older requests return early Loader-->>Catalog: catalogs.set('fr', catalog) Plugin->>Cookie: setLocale('fr') → writes cookie Plugin-->>State: "revision.value += 1" T->>State: getLocale() → reads revision (reactive) T->>State: getCatalog() → reads revision (reactive) T->>Renderer: translateVueChildren(vnodes, state, options) Renderer->>Renderer: createSourceNodes → serializeNodes → hashSource Renderer->>Catalog: getCatalog()[hash] Renderer-->>T: translated VNodeChild ``` </details> <sub>Reviews (13): Last reviewed commit: ["fix(vue): persist locale in browser cook..."](163973a) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=48899958)</sub> <!-- /greptile_comment -->
eoinest
force-pushed
the
e/vue/add-spa-ssr-examples
branch
from
August 7, 2026 00:53
714a960 to
3b4d50c
Compare
eoinest
force-pushed
the
e/vue/add-spa-ssr-examples
branch
from
August 7, 2026 02:19
3b4d50c to
ca3be55
Compare
eoinest
force-pushed
the
e/vue/add-spa-ssr-examples
branch
from
August 7, 2026 02:44
ca3be55 to
a6835d5
Compare
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.
Summary
gt-vuepackages and local catalogs.Suspense,Transition,Teleport, formatters,msg(), Vue TSX, and locale-prefixed navigation.gt-vuein the scheduled workflow.Testing
pnpm install --frozen-lockfile— passedpnpm --filter vite-vue-ssr generate— passed (33 extracted entries, stable across repeated generation)pnpm --filter vite-vue-ssr validate— passedpnpm --filter vite-vue-ssr typecheck— passedpnpm --filter vite-vue-ssr test— passed (2 tests, including 128 overlapping English/French requests)pnpm --filter vite-vue-ssr build— passed (client and SSR production bundles)GT_TEST_APPS=vite-vue,vite-vue-ssr pnpm --filter gt-test-apps-e2e test:e2e— passed in Chromiumpnpm --filter gt-test-apps-e2e lint— passedpnpm --filter gt-test-apps-e2e typecheck— passedNotes
Greptile Summary
This PR adds a Vite 8 / Vue 3.5 / Vue Router 5 SSR documentation-shell example (
vite-vue-ssr) that demonstrates per-request translation state isolation, client-side hydration, lazy routes, scoped slots,Suspense,Transition,Teleport, formatters,msg(), and Vue TSX compatibility. It also wires both Vue examples into the persistent Chromium E2E matrix and addsgt-vueto the scheduled cron build.render()constructs a freshGTPlugin, router, and Vue app instance, verified by 128 concurrent overlapping English/French renders in the unit test suite and 8 parallel requests in the E2E test.type="application/json"script tag with\u003c-escaped JSON and read back inentry-client.tswith a URL-derived fallback, preventing mismatch on initial paint.vite-vue*apps via an additive condition that does not alter behaviour for existing non-Vue tests.Confidence Score: 5/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant B as Browser participant S as server.ts (Express) participant ES as entry-server.ts participant A as app.ts participant R as router.ts participant T as translations.ts participant VR as vue/server-renderer B->>S: GET /fr/reference S->>ES: render('/fr/reference') ES->>R: getLocaleFromUrl('/fr/reference') → 'fr' ES->>A: createDocsGT('fr') ES->>A: createDocsApp('/fr/reference', true, gt) A->>R: "createDocsRouter(server=true)" A->>R: router.push('/fr/reference') Note over A,R: beforeResolve guard fires A->>A: gt.setLocale('fr') A->>T: loadTranslations('fr') T-->>A: fr.json catalog A-->>ES: "{ app, gt, router }" ES->>VR: renderToString(app, context) VR-->>ES: "{ html, teleports }" ES-->>S: "{ html, locale:'fr', teleports }" S->>S: inject locale/html/teleports/state into index.html S-->>B: 200 text/html (SSR-rendered French page) Note over B: Hydration B->>B: "readState() → { locale: 'fr' } from #gt-state" B->>A: createDocsGT('fr') B->>A: createDocsApp(url, false, gt) B->>B: "app.mount('#app')" B->>B: "data-hydrated='true'"Reviews (8): Last reviewed commit: "test(vue): validate TSX and per-request ..." | Re-trigger Greptile