feat(i18n): add locale-aware routing and multilingual UI copy#84
feat(i18n): add locale-aware routing and multilingual UI copy#84agualdron wants to merge 7 commits into
Conversation
* 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
|
| 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]
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
- 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
|
@greptile |
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.
|
@greptile |
| 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> { |
There was a problem hiding this 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)
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.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*.
|
@greptile |
Summary
Add locale-aware routing and multilingual copy across TradingGoose and docs using
next-intlplus Lingo-backed translation bundles. Public pages, auth flows, workspace bootstrap, blog content, sitemaps, link headers, and metadata now resolveen,es, andzh-CNconsistently, with/zhas 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
/zhAffected Areas
apps/tradinggooseapps/docsValidation
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 ...inapps/tradinggoose: 28 files passed, 92 tests passed.bunx vitest run proxy.test.ts lib/i18n.test.tsinapps/docs: 2 files passed, 4 tests passed.act(...)warnings appeared in the app test run, but the suites completed green.Review Focus
apps/tradinggoose/proxy.tsandapps/docs/proxy.tsReview Notes
i18n/helpers, reusing existing route/layout entrypoints, and keeping tests colocated with the changed code.Risk / Rollout Notes
/zh; old public/zh-CNroutes are rejected by design.Config / Data Changes
next-intlrequest context and Lingo translation bundlesScreenshots / Video
Checklist