Skip to content

Commit 67e9e42

Browse files
kubo6472cursoragentcoderabbitai[bot]CodeRabbit
authored
fix(web): load UI locale from repo-root .env and CI build (#423)
* fix(web): load UI locale from repo-root .env and CI build NUXT_PUBLIC_UI_LOCALE is baked at Nuxt build time. The monorepo keeps .env.example at the repo root, but Nuxt only auto-loaded packages/web/.env, so setting NUXT_PUBLIC_UI_LOCALE=sk in root .env had no effect. - Load repo-root .env in nuxt.config before reading runtimeConfig - Resolve locale via runtimeConfig fallback; use proxy strings in all modes - Pass NUXT_PUBLIC_UI_LOCALE through deploy.yml web build step - Document build-time requirement in workers-deploy-env.md Co-authored-by: Jakub Doboš <[email protected]> * fix: apply CodeRabbit auto-fixes Fixed 2 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit <[email protected]> * fix(ci): restore package-lock.json after CodeRabbit auto-edit CodeRabbit commit 87d0f7c removed nested lockfile entries required by npm ci. Restore lockfile from main; keep the resolveUiLocale cache fix. Co-authored-by: Jakub Doboš <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jakub Doboš <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <[email protected]>
1 parent 54c4139 commit 67e9e42

6 files changed

Lines changed: 77 additions & 10 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
EXPECTED_ALLOWED_ORIGINS: ${{ vars.ALLOWED_ORIGINS_STAGING }}
3131
NUXT_PUBLIC_DEPLOY_TIER: staging
3232
NUXT_PUBLIC_GIT_COMMIT: ${{ github.sha }}
33+
NUXT_PUBLIC_UI_LOCALE: ${{ vars.NUXT_PUBLIC_UI_LOCALE }}
3334
NUXT_PUBLIC_SENTRY_DSN: ${{ vars.NUXT_PUBLIC_SENTRY_DSN }}
3435
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
3536
steps:
@@ -60,6 +61,7 @@ jobs:
6061
env:
6162
API_URL: ${{ env.API_BASE_URL }}
6263
NUXT_PUBLIC_SITE_URL: ${{ env.FRONTEND_URL }}
64+
NUXT_PUBLIC_UI_LOCALE: ${{ env.NUXT_PUBLIC_UI_LOCALE }}
6365
NODE_ENV: production
6466
run: npm run build
6567

@@ -131,6 +133,7 @@ jobs:
131133
NUXT_PUBLIC_DEPLOY_TIER: production
132134
NUXT_PUBLIC_APP_VERSION: ${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '' }}
133135
NUXT_PUBLIC_GIT_COMMIT: ${{ github.sha }}
136+
NUXT_PUBLIC_UI_LOCALE: ${{ vars.NUXT_PUBLIC_UI_LOCALE }}
134137
NUXT_PUBLIC_SENTRY_DSN: ${{ vars.NUXT_PUBLIC_SENTRY_DSN }}
135138
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
136139
steps:
@@ -167,6 +170,7 @@ jobs:
167170
env:
168171
API_URL: ${{ env.API_BASE_URL }}
169172
NUXT_PUBLIC_SITE_URL: ${{ env.FRONTEND_URL }}
173+
NUXT_PUBLIC_UI_LOCALE: ${{ env.NUXT_PUBLIC_UI_LOCALE }}
170174
NODE_ENV: production
171175
run: npm run build
172176

packages/web/docs/workers-deploy-env.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Read in `nuxt.config.ts` via `process.env` and embedded into the client/server b
1515
|----------|----------|-----------|
1616
| `API_URL` | `runtimeConfig.public.apiUrl` | `vars.API_URL_STAGING` / `vars.API_URL_PROD` |
1717
| `NUXT_PUBLIC_SITE_URL` | `runtimeConfig.public.siteUrl`, OG URLs | `vars.FRONTEND_URL_STAGING` / `vars.FRONTEND_URL_PROD` |
18+
| `NUXT_PUBLIC_UI_LOCALE` | UI copy catalog (`en`, `sk`, `cs`) — **baked at build**; runtime Worker vars do not change language | `vars.NUXT_PUBLIC_UI_LOCALE` (defaults to `en` when unset) |
1819
| `NUXT_PUBLIC_DEPLOY_TIER` | Admin footer build label | `staging` / `production` in `deploy.yml` |
1920
| `NUXT_PUBLIC_GIT_COMMIT` | Admin footer git SHA | `${{ github.sha }}` |
2021
| `NUXT_PUBLIC_APP_VERSION` | Admin footer on production tags | tag name on prod deploy |
@@ -26,6 +27,12 @@ Read in `nuxt.config.ts` via `process.env` and embedded into the client/server b
2627

2728
No server-only secrets are required for the current Nuxt app. All config used at runtime today is in `runtimeConfig.public` (build-time).
2829

30+
`NUXT_PUBLIC_UI_LOCALE` must be present when `npm run build` runs (CI build step or local command). Setting it only on the Cloudflare Worker after deploy has no effect — rebuild and redeploy.
31+
32+
### Local dev
33+
34+
Copy `.env.example` to the **repo root** `.env` (or `packages/web/.env`) and set `NUXT_PUBLIC_UI_LOCALE=sk`. Restart `nuxi dev` after changing locale. The web package also loads the repo-root `.env` so root and `packages/web/.env` both work.
35+
2936
## GitHub Actions secrets (web Worker deploy)
3037

3138
Uses the same environment-scoped tokens as the API Worker:

packages/web/nuxt.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { readBuildInfoDefaults } from './utils/buildInfoSource'
2+
import { loadMonorepoRootEnv } from './utils/loadMonorepoRootEnv'
23
import { parseEnvBoolean, parseTracesSampleRate } from './utils/sentryOptions'
34

5+
loadMonorepoRootEnv()
6+
47
const buildInfo = readBuildInfoDefaults()
58

69
export default defineNuxtConfig({
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { existsSync } from 'node:fs'
2+
import { resolve, dirname } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
import { config as loadDotenv } from 'dotenv'
5+
6+
/**
7+
* Load repo-root `.env` into `process.env` when keys are not already set.
8+
*
9+
* `.env.example` lives at the monorepo root, but Nuxt only auto-loads
10+
* `packages/web/.env`. Without this, `NUXT_PUBLIC_UI_LOCALE` and other
11+
* shared vars copied to root `.env` are ignored during `nuxi dev` / `nuxi build`.
12+
*/
13+
export function loadMonorepoRootEnv(): void {
14+
const monorepoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
15+
const rootEnvPath = resolve(monorepoRoot, '.env')
16+
if (!existsSync(rootEnvPath)) return
17+
18+
const parsed = loadDotenv({ path: rootEnvPath, processEnv: {} }).parsed
19+
if (!parsed) return
20+
21+
for (const [key, value] of Object.entries(parsed)) {
22+
if (process.env[key] === undefined) {
23+
process.env[key] = value
24+
}
25+
}
26+
}

packages/web/utils/resolveUiLocale.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,38 @@ export const DEV_UI_LOCALE_COOKIE = 'vmp_dev_ui_locale'
99
* UI locale baked into this build from `NUXT_PUBLIC_UI_LOCALE`.
1010
* Production instances use this exclusively.
1111
*/
12+
function readUiLocaleFromEnv(): string | undefined {
13+
const fromImportMeta =
14+
typeof import.meta !== 'undefined' ? import.meta.env?.NUXT_PUBLIC_UI_LOCALE : undefined
15+
if (typeof fromImportMeta === 'string' && fromImportMeta) return fromImportMeta
16+
17+
const fromProcess = typeof process !== 'undefined' ? process.env.NUXT_PUBLIC_UI_LOCALE : undefined
18+
if (typeof fromProcess === 'string' && fromProcess) return fromProcess
19+
20+
return undefined
21+
}
22+
1223
export function getBuildUiLocale(): UiLocale {
1324
if (cachedBuildLocale) return cachedBuildLocale
14-
const fromEnv =
15-
(typeof import.meta !== 'undefined' && import.meta.env?.NUXT_PUBLIC_UI_LOCALE) ||
16-
(typeof process !== 'undefined' ? process.env.NUXT_PUBLIC_UI_LOCALE : undefined)
17-
cachedBuildLocale = parseUiLocale(typeof fromEnv === 'string' ? fromEnv : undefined)
18-
return cachedBuildLocale
25+
26+
let fromEnv = readUiLocaleFromEnv()
27+
if (!fromEnv) {
28+
try {
29+
const config = useRuntimeConfig()
30+
const fromRuntime = config.public.uiLocale
31+
if (typeof fromRuntime === 'string' && fromRuntime) fromEnv = fromRuntime
32+
} catch {
33+
// Outside Nuxt setup (e.g. static import timing) — fall through to default.
34+
}
35+
}
36+
37+
if (fromEnv) {
38+
cachedBuildLocale = parseUiLocale(fromEnv)
39+
return cachedBuildLocale
40+
}
41+
42+
// No valid locale found — return fallback without caching.
43+
return parseUiLocale(undefined)
1944
}
2045

2146
/**

packages/web/utils/strings.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@
55
* Development: optional cookie override via the locale preview bar for in-context review.
66
*/
77
import type { Strings } from '~/locales'
8-
import { getActiveLocaleCatalog, getBuildLocaleCatalog } from '~/utils/resolveUiLocale'
8+
import { getActiveLocaleCatalog } from '~/utils/resolveUiLocale'
99

1010
export type { PaymentProvider, PlanType, Strings } from '~/locales'
1111

12-
function createDevStringsProxy(): Strings {
12+
function createStringsProxy(): Strings {
1313
return new Proxy({} as Strings, {
1414
get(_target, prop) {
15-
const value = getActiveLocaleCatalog().strings[prop as keyof Strings]
15+
const catalog = getActiveLocaleCatalog()
16+
const value = catalog.strings[prop as keyof Strings]
1617
if (typeof value === 'function') {
17-
return (value as (...args: unknown[]) => unknown).bind(getActiveLocaleCatalog().strings)
18+
return (value as (...args: unknown[]) => unknown).bind(catalog.strings)
1819
}
1920
return value
2021
},
2122
})
2223
}
2324

24-
export default import.meta.dev ? createDevStringsProxy() : getBuildLocaleCatalog().strings
25+
/** Resolves against the active locale on each access (build default or dev cookie override). */
26+
export default createStringsProxy()

0 commit comments

Comments
 (0)