Skip to content

feat(i18n): add locale-aware routing and multilingual UI copy#84

Draft
agualdron wants to merge 7 commits into
mainfrom
internationalization
Draft

feat(i18n): add locale-aware routing and multilingual UI copy#84
agualdron wants to merge 7 commits into
mainfrom
internationalization

Conversation

@agualdron
Copy link
Copy Markdown
Collaborator

Summary

Add locale-aware routing and multilingual copy across TradingGoose and docs using next-intl plus Lingo-backed translation bundles. Public pages, auth flows, workspace bootstrap, blog content, sitemaps, link headers, and metadata now resolve en, es, and zh-CN consistently, with /zh as the public Chinese prefix.

Why

This makes the public site and docs available in multiple languages, keeps canonical URLs and SEO metadata aligned with the active locale, and carries locale context through auth, workspace, and markdown/discovery flows without adding legacy backfill paths.

Type of Changes

  • New feature
  • Breaking change: public Chinese URLs now use /zh
  • Documentation / content update

Affected Areas

  • apps/tradinggoose
  • apps/docs
  • Dev tooling / CI / infra
  • Other: i18n / localization plumbing

Validation

  • bunx vitest run proxy.test.ts app/sitemap.test.ts i18n/public-copy.test.ts i18n/utils.test.ts app/api/workspaces/route.test.ts ... in apps/tradinggoose: 28 files passed, 92 tests passed.
  • bunx vitest run proxy.test.ts lib/i18n.test.ts in apps/docs: 2 files passed, 4 tests passed.
  • A few jsdom act(...) warnings appeared in the app test run, but the suites completed green.

Review Focus

  • Locale normalization and redirects in apps/tradinggoose/proxy.ts and apps/docs/proxy.ts
  • Locale propagation through workspace/auth API headers, redirects, and callback URLs
  • Locale fallback behavior in blog post loading, public copy selection, sitemaps, and metadata alternates

Review Notes

  • Style: follows existing repo patterns by centralizing locale logic in i18n/ helpers, reusing existing route/layout entrypoints, and keeping tests colocated with the changed code.
  • Self-review: completed this pass.
  • Tests: added/updated tests are passing in the targeted runs above.

Risk / Rollout Notes

  • Public Chinese URLs are intentionally canonicalized to /zh; old public /zh-CN routes are rejected by design.
  • Review sitemap entries, canonical tags, and locale-prefixed redirects before merging.
  • No migration or backfill path was added.

Config / Data Changes

  • Env vars added or changed: none
  • Database schema or migration impact: none
  • External services or provider behavior changed: locale-aware routing now depends on next-intl request context and Lingo translation bundles

Screenshots / Video

  • Not captured in this run.

Checklist

  • I kept the change focused and reviewed my own diff
  • I validated the change locally and documented the results above
  • I updated docs, examples, or copy if behavior/user-facing flows changed
  • I called out any env, schema, provider, or rollout impact
  • I did not include secrets, tokens, or private credentials in this PR

agualdron and others added 4 commits April 25, 2026 13:19
* build(docker)!: modernize image builds and compose deployment config
Pin the Bun toolchain and runtime dependencies, split guardrails setup into a dedicated build stage, and bundle the realtime socket server into a standalone runtime artifact.
Collapse the image workflow into a single multi-platform job that publishes GHCR everywhere and Docker Hub on main. Update the compose stacks, env templates, and docs to require explicit secrets, add Redis, and pin Ollama and prod image tags.
BREAKING CHANGE: Docker Compose now requires explicit *POSTGRES_* credentials, *NEXT_PUBLIC_APP_URL*, *BETTER_AUTH_SECRET*, *ENCRYPTION_KEY*, *INTERNAL_API_SECRET*, and tagged images such as *IMAGE_TAG* and *OLLAMA_IMAGE_TAG* instead of relying on the old implicit defaults.

* chore(docker): trim env template whitespace

* fix(docker): align compose defaults and guardrails stage

Update the Docker Compose inputs so they match the current runtime contract:

- switch the Docker env example to *postgres://* for *DATABASE_URL*
- keep *redis* internal to Compose instead of publishing *6379* on the host
- default *NEXT_PUBLIC_SOCKET_URL* to *http://realtime:3002* in Ollama and prod
- add *API_ENCRYPTION_KEY* to the local Compose environment
- run the guardrails setup from *apps/tradinggoose/lib/guardrails* so the virtualenv is created beside the script

* fix(docker): use browser-accessible socket URLs in compose

Update the Docker Compose docs and env template to use *apps/tradinggoose/.env* with *--env-file*, point local runs at *http://localhost:3002*, require production to set *NEXT_PUBLIC_SOCKET_URL* explicitly, and document *IMAGE_TAG* and *OLLAMA_IMAGE_TAG* in the Docker env file.

Tighten the app runtime image by copying only the Bun *lib0* workspace symlink and by copying the guardrails assets from the build-stage paths that *setup.sh* writes.

* fix(docker): slim runtime copies and create realtime build dir (#81)

Remove the full root *node_modules* copy from the app runner image and keep only the Bun *lib0* workspace target plus the matching workspace symlink path needed by Yjs.

Create */tmp/realtime-build* before running *bun build* so the realtime bundle step can write *socket-server.js* in a fresh Alpine layer.

* fix(docker-compose): restore GPU device reservation for ollama (#81)

Replace *gpus: all* with the Compose device reservation block so the *ollama* GPU profile actually receives NVIDIA devices at runtime.

This keeps the file within the Compose spec and preserves GPU-backed Ollama behavior without relying on a Docker CLI-only flag.

* chore(docker): clarify env template comments (#81)

Move the setup notes for *BETTER_AUTH_SECRET*, *ENCRYPTION_KEY*, *INTERNAL_API_SECRET*, and *OLLAMA_URL* onto separate lines in *apps/tradinggoose/.env.example.docker* for consistency and readability.
* fix(copilot): normalize reasoning envelopes

* fix(copilot): hide unknown thought duration

* fix(copilot): clarify finished thinking label

* fix(copilot): render thinking markdown

* fix(copilot): preserve reasoning with content blocks

* fix(copilot): stabilize reasoning block timestamps
* feat(widgets): add portfolio snapshot widget

* feat(widgets): add quick order widget

Co-authored-by: Codex <codex@openai.com>

* feat(widgets): add heatmap market APIs

* feat(widgets): add heatmap widget

* fix(widgets): keep quote snapshot contract client safe

* fix(widgets): simplify trading account selection

* refactor(widgets): unify provider header controls

* refactor(widgets): consolidate provider APIs and portfolio UI

* refactor(widgets): stream market quote snapshots

* refactor(widgets): require explicit market providers

* feat(widgets): stream shared trading portfolio data

Co-authored-by: Codex <codex@openai.com>

* refactor(market): centralize TradingGoose Market requests

Co-authored-by: Codex <codex@openai.com>

* refactor(auth): normalize credential service resolution

Co-authored-by: Codex <codex@openai.com>

* refactor(trading): route portfolio requests by credential service

Co-authored-by: Codex <codex@openai.com>

* refactor(widgets): persist trading credential services

Co-authored-by: Codex <codex@openai.com>

* docs(changelog): update April feature notes

Co-authored-by: Codex <codex@openai.com>

* fix(workflow): clarify output select icon state

* refactor(indicators): run chart indicators in browser

Co-authored-by: Codex <codex@openai.com>

* refactor(widgets): simplify shared widget context

* feat(widgets): refine portfolio widgets and workflow layout

* test(workflow): update auto layout expectations

* fix(trading): align Alpaca crypto availability

Co-authored-by: Codex <codex@openai.com>

* docs(changelog): document portfolio widgets branch

Co-authored-by: Codex <codex@openai.com>

* feat(portfolio): enhance trading performance window support and widget rendering

---------

Co-authored-by: Codex <codex@openai.com>
- Add *next-intl* and Lingo wiring for locale resolution, request config, and navigation helpers.
- Translate docs, landing, auth, workspace, admin, and widget surfaces for *en*, *es*, and *zh-CN*.
- add English, Spanish, and Simplified Chinese catalogs for public pages, auth, blog, workspace, and admin surfaces
- Update proxy, sitemap, and URL helpers so locale-prefixed routes, markdown, and discovery links stay consistent.
- add tests for locale helpers, routing, and localized UI flows
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

This PR wires next-intl locale-aware routing across the full TradingGoose and docs stacks: proxy middleware now strips/rewrites locale prefixes, sets X-NEXT-INTL-LOCALE headers, and enforces English-only redirects; auth flows, workspace bootstrap, blog, sitemaps, LLM-discovery markdown, and link headers all resolve en/es/zh-CN via a shared i18n/utils helper layer. The /zh public prefix for Chinese is canonicalized in both apps consistently.

The most significant previously-flagged P1s (double-locale on verify redirect, callback URL early return, workspace personalization, sitemap /careers, missing changelog) are tracked in earlier review threads. One new finding: renderPublicPageMarkdown's default branch passes the raw locale-prefixed pathname to buildConvertedPageMarkdown instead of normalizedPathname; this is a low-impact P2 today because all real paths that reach it are English-only and redirected before the markdown rewriter fires.

Confidence Score: 4/5

Safe to merge once the P1s flagged in earlier review threads are resolved; the new finding in this pass is P2 only.

Multiple P1s were flagged in previous review rounds (double-locale on verify, callback URL early-return, sitemap /careers, missing changelog). The only net-new finding in this pass is a P2 (wrong pathname variable in the markdown default branch). Score is capped at 4 due to unresolved P1s from prior threads.

apps/tradinggoose/lib/markdown/public-page-markdown.ts (new P2 finding); files cited in previous review threads (use-verification.ts, app/workspace/page.tsx, app/sitemap.ts, app/api/workspaces/route.ts) for P1 follow-up.

Important Files Changed

Filename Overview
apps/tradinggoose/proxy.ts Locale-aware middleware refactor: adds stripLocaleFromPathname, ENGLISH_ONLY_PUBLIC_ROUTES redirect, buildLocaleAwareResponse helper, and locale-header injection — logic looks correct overall
apps/tradinggoose/i18n/utils.ts New locale utility module: localizePathname, stripLocaleFromPathname, buildLocaleRequestHeaders — clean, well-typed, consistent across the codebase
apps/tradinggoose/app/api/workspaces/route.ts Adds locale header reading and passes locale to workspace creation for i18n-aware default names and workflow descriptions; first-name personalization is preserved
apps/tradinggoose/app/workspace/page.tsx Locale-aware workspace redirect page; callback URL double-prefix guard added correctly (inner if prevents early return); removes useSearchParams hook but reads window.location.search directly
apps/tradinggoose/lib/markdown/public-page-markdown.ts Locale-aware markdown generation for LLM-discovery routes; default case passes raw locale-prefixed pathname to buildConvertedPageMarkdown instead of the normalized path
apps/tradinggoose/app/(auth)/verify/use-verification.ts useRouter correctly imported from next/navigation (not the locale-aware wrapper); localizeHref used explicitly for post-verification redirect — pattern is correct
apps/tradinggoose/app/sitemap.ts Sitemap expanded to locale-aware URLs for localizedRoutes and blog posts; /careers included as English-only; docs anchor preserved
apps/docs/proxy.ts 404s /zh-CN/ paths and rewrites /zh/ to /zh-CN/ internally before handing off to fumadocs i18nProxy — clean and correct
apps/docs/lib/i18n.ts Language list updated from zh to zh-CN; new stripLocaleFromPathname/localizePathname helpers that map zh-CN ↔ /zh public segment
apps/tradinggoose/i18n/routing.ts next-intl routing config: as-needed prefix mode, zh-CN mapped to /zh public prefix, locale detection disabled

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request] --> B{Path = /zh-CN/*?}
    B -->|Yes| C[404 Not Found]
    B -->|No| D[stripLocaleFromPathname]
    D --> E{Protected path?}
    E -->|Yes, no session| F[Redirect to locale/login]
    E -->|No or has session| G{Auth route?}
    G -->|reauth| H[Clear cookies + locale response]
    G -->|has session| I[Redirect to locale/workspace]
    G -->|No| J{Security filter}
    J -->|Blocked| K[Block response]
    J -->|Pass| L{English-only route with locale prefix?}
    L -->|Yes| M[Redirect to /canonical]
    L -->|No| N{Markdown request?}
    N -->|Yes| O[Rewrite to /api/markdown?path=locale-path]
    N -->|No| P[buildLocaleAwareResponse]
    P --> Q{hasLocalePrefix?}
    Q -->|Yes, en default| R[Redirect to clean path]
    Q -->|Yes, non-default| S[Rewrite + X-NEXT-INTL-LOCALE header]
    Q -->|No| T[next + locale header]
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/tradinggoose/lib/markdown/public-page-markdown.ts:270-274
The `default` branch passes the original `pathname` (e.g. `/zh/terms`) to `buildConvertedPageMarkdown` instead of `normalizedPathname`. The function constructs the fetch URL as `${origin}${pathname}`, so it requests the locale-prefixed URL and the canonical `url:` in the resulting markdown document will also carry the prefix. All paths that currently reach this branch at runtime are English-only (redirected by the proxy before the markdown rewriter fires), so the practical impact is low — but any future markdown-renderable route outside the explicit cases would silently produce wrong canonical URLs.

```suggestion
      if (normalizedPathname.startsWith('/blog/')) {
        return buildBlogPostMarkdown(origin, normalizedPathname, locale)
      }

      return buildConvertedPageMarkdown(origin, normalizedPathname)
```

Reviews (4): Last reviewed commit: "fix(workspace): avoid early exit on loca..." | Re-trigger Greptile

Comment thread apps/tradinggoose/app/(auth)/verify/use-verification.ts
Comment thread apps/tradinggoose/app/sitemap.ts
- switch verification redirects to *next/navigation* so *localizeHref* is applied once
- fix the invitation route indentation and keep the existing auth flow intact
- keep */careers* in the sitemap with an explicit note that the page is live
- normalize *bun.lock* after the tooling pass
@agualdron
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/tradinggoose/app/api/workspaces/route.ts Outdated
Use the first token from *session.user.name* when auto-bootstrapping a new
workspace, and keep the localized *workspace.defaults.newWorkspaceName* fallback
when no name is available.

Add coverage for both the personalized and fallback bootstrap paths.
@agualdron
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/tradinggoose/app/workspace/page.tsx
Comment on lines 1 to 5
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/app/(landing)/blog/lib/posts'
import { getBlogPostIndex, getPostsFromIndex } from '@/app/(landing)/blog/lib/posts'
import { locales, localizeUrl } from '@/i18n/utils'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
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.

P1 Missing changelog entry for staging-targeting PR

Custom rule auto-83a2bb60 requires every PR targeting staging to add exactly one new dated markdown file under changelog/. This PR adds 237 changed files but includes no new entry in changelog/. This change will be invisible in the dated changelog audit trail.

Rule Used: Any PR targeting staging must add exactly one ne... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/tradinggoose/app/sitemap.ts
Line: 1-5

Comment:
**Missing changelog entry for `staging`-targeting PR**

Custom rule `auto-83a2bb60` requires every PR targeting `staging` to add exactly one new dated markdown file under `changelog/`. This PR adds 237 changed files but includes no new entry in `changelog/`. This change will be invisible in the dated changelog audit trail.

**Rule Used:** Any PR targeting `staging` must add exactly one ne... ([source](https://app.greptile.com/review/custom-context?memory=auto-83a2bb60))

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Keep *redirectToFirstWorkspace* falling through to the workspace fetch when a localized *callbackUrl* resolves to the current *pathname* instead of returning before navigation.

Add regression coverage for the locale-prefixed workspace root case in *apps/tradinggoose/app/workspace/page.test.tsx* and record the required branch note in *changelog/May-04-2026.md*.
@agualdron
Copy link
Copy Markdown
Collaborator Author

@greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant