From 4258aecadd299f974d797ef082048190d8db060c Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Tue, 16 Jun 2026 11:07:34 +0530 Subject: [PATCH 1/8] feat: localization initial commit --- package-lock.json | 243 ++++++++++++++++++ package.json | 8 +- web-common/src/features/i18n/messages/de.json | 5 + web-common/src/features/i18n/messages/en.json | 5 + .../features/i18n/project.inlang/.gitignore | 19 ++ .../features/i18n/project.inlang/.meta.json | 3 + .../features/i18n/project.inlang/README.md | 103 ++++++++ .../i18n/project.inlang/settings.json | 12 + .../src/layout/ApplicationHeader.svelte | 3 + web-local/src/app.html | 2 +- web-local/src/hooks.server.ts | 18 ++ web-local/src/hooks.ts | 6 + web-local/vite.config.ts | 10 +- 13 files changed, 433 insertions(+), 4 deletions(-) create mode 100644 web-common/src/features/i18n/messages/de.json create mode 100644 web-common/src/features/i18n/messages/en.json create mode 100644 web-common/src/features/i18n/project.inlang/.gitignore create mode 100644 web-common/src/features/i18n/project.inlang/.meta.json create mode 100644 web-common/src/features/i18n/project.inlang/README.md create mode 100644 web-common/src/features/i18n/project.inlang/settings.json create mode 100644 web-local/src/hooks.server.ts create mode 100644 web-local/src/hooks.ts diff --git a/package-lock.json b/package-lock.json index d18f5b7eb96f..73060352e9a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "web-local" ], "devDependencies": { + "@inlang/paraglide-js": "^2.10.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@types/eslint": "^8.56.9", "@vitest/eslint-plugin": "^1.1.42", @@ -6242,6 +6243,93 @@ "mlly": "^1.8.0" } }, + "node_modules/@inlang/paraglide-js": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@inlang/paraglide-js/-/paraglide-js-2.10.0.tgz", + "integrity": "sha512-3xQveEyZMV9IOLP7Vy9Ttye+Yzryqz6KM06tvVwvmbCPDTdzmFoc34KlREXGpHuBAlxRZGfAhcJKfnSXXQDmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inlang/recommend-sherlock": "^0.2.1", + "@inlang/sdk": "2.6.2", + "commander": "11.1.0", + "consola": "3.4.0", + "json5": "2.2.3", + "unplugin": "^2.1.2", + "urlpattern-polyfill": "^10.0.0" + }, + "bin": { + "paraglide-js": "bin/run.js" + } + }, + "node_modules/@inlang/paraglide-js/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@inlang/paraglide-js/node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@inlang/recommend-sherlock": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@inlang/recommend-sherlock/-/recommend-sherlock-0.2.1.tgz", + "integrity": "sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-json": "^4.2.3" + } + }, + "node_modules/@inlang/sdk": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@inlang/sdk/-/sdk-2.6.2.tgz", + "integrity": "sha512-eOgAX+eQpHvD/H4BMILc4tZ85XviTlwr/51RKkKUHozVVthj5avUPKP+4N4vcTUrqSscl2atTh9NbNTuvoBN0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@lix-js/sdk": "0.4.7", + "@sinclair/typebox": "^0.31.17", + "kysely": "^0.27.4", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^13.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@inlang/sdk/node_modules/@sinclair/typebox": { + "version": "0.31.28", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz", + "integrity": "sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inlang/sdk/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/@internationalized/date": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.0.tgz", @@ -7019,6 +7107,46 @@ "@lezer/lr": "^1.4.0" } }, + "node_modules/@lix-js/sdk": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@lix-js/sdk/-/sdk-0.4.7.tgz", + "integrity": "sha512-pRbW+joG12L0ULfMiWYosIW0plmW4AsUdiPCp+Z8rAsElJ+wJ6in58zhD3UwUcd4BNcpldEGjg6PdA7e0RgsDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@lix-js/server-protocol-schema": "0.1.1", + "dedent": "1.5.1", + "human-id": "^4.1.1", + "js-sha256": "^0.11.0", + "kysely": "^0.27.4", + "sqlite-wasm-kysely": "0.3.0", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@lix-js/sdk/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@lix-js/server-protocol-schema": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@lix-js/server-protocol-schema/-/server-protocol-schema-0.1.1.tgz", + "integrity": "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -9107,6 +9235,16 @@ ], "license": "MIT" }, + "node_modules/@sqlite.org/sqlite-wasm": { + "version": "3.48.0-build4", + "resolved": "https://registry.npmjs.org/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.48.0-build4.tgz", + "integrity": "sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "sqlite-wasm": "bin/index.js" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -12517,6 +12655,13 @@ "dev": true, "license": "MIT" }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -14255,6 +14400,20 @@ "node": ">=18" } }, + "node_modules/comment-json": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -15945,6 +16104,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -19775,6 +19949,16 @@ "node": ">= 14" } }, + "node_modules/human-id": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", + "integrity": "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -20877,6 +21061,13 @@ "node": ">=0.10.0" } }, + "node_modules/js-sha256": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz", + "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -21184,6 +21375,16 @@ "dev": true, "license": "MIT" }, + "node_modules/kysely": { + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.6.tgz", + "integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/langium": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", @@ -30803,6 +31004,18 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sqlite-wasm-kysely": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sqlite-wasm-kysely/-/sqlite-wasm-kysely-0.3.0.tgz", + "integrity": "sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==", + "dev": true, + "dependencies": { + "@sqlite.org/sqlite-wasm": "^3.48.0-build2" + }, + "peerDependencies": { + "kysely": "*" + } + }, "node_modules/srcset": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", @@ -33292,6 +33505,22 @@ "node": ">= 0.8" } }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -33528,6 +33757,13 @@ "dev": true, "license": "BSD" }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -34881,6 +35117,13 @@ "node": ">=10.13.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index 0bee62bf7584..102b7351791f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "web-local" ], "scripts": { + "build:i18n": "paraglide-js compile --project web-common/src/features/i18n/project.inlang --outdir web-common/src/features/i18n/gen", "build": "npm run build -w web-local", "dev": "sh -c 'npm run dev-runtime -- \"$@\" & npm run dev-web -- --port 3001 & wait' --", "dev-web": "npm run dev -w web-local -- ", @@ -21,9 +22,11 @@ "gen:colors": "node web-common/src/features/themes/gen-colors.ts", "quality": "bash ./scripts/web-test-code-quality.sh", "quality:ci": "FAIL_FAST=true bash ./scripts/web-test-code-quality.sh", - "check:edit-route-parity": "node ./scripts/check-edit-route-parity.js" + "check:edit-route-parity": "node ./scripts/check-edit-route-parity.js", + "machine-translate": "inlang machine translate --project web-common/src/features/i18n/project.inlang" }, "devDependencies": { + "@inlang/paraglide-js": "^2.10.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@types/eslint": "^8.56.9", "@vitest/eslint-plugin": "^1.1.42", @@ -37,7 +40,8 @@ "sass": "^1.88.0", "syncpack": "^13.0.0", "typescript": "^5.6.2", - "typescript-eslint": "^8.30.1" + "typescript-eslint": "^8.30.1", + "@inlang/cli": "^3.0.0" }, "lint-staged": { "*.{svelte,ts}": [ diff --git a/web-common/src/features/i18n/messages/de.json b/web-common/src/features/i18n/messages/de.json new file mode 100644 index 000000000000..4e0623bd2d5f --- /dev/null +++ b/web-common/src/features/i18n/messages/de.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "example_message": "Guten Tag {username}", + "example_title": "Rill Projekt" +} diff --git a/web-common/src/features/i18n/messages/en.json b/web-common/src/features/i18n/messages/en.json new file mode 100644 index 000000000000..2162954813df --- /dev/null +++ b/web-common/src/features/i18n/messages/en.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "example_message": "Hello world {username}", + "example_title": "Rill project" +} diff --git a/web-common/src/features/i18n/project.inlang/.gitignore b/web-common/src/features/i18n/project.inlang/.gitignore new file mode 100644 index 000000000000..433b48c811f1 --- /dev/null +++ b/web-common/src/features/i18n/project.inlang/.gitignore @@ -0,0 +1,19 @@ +# IF GIT SHOWED THAT THIS FILE CHANGED +# +# 1. RUN THE FOLLOWING COMMAND +# +# --- +# git rm --cached '**/*.inlang/.gitignore' +# --- +# +# 2. COMMIT THE CHANGE +# +# --- +# git commit -m "fix: remove tracked .gitignore from inlang project" +# --- +# +# Inlang handles the gitignore itself starting with version ^2.5. +# +# everything is ignored except settings.json +cache +!settings.json \ No newline at end of file diff --git a/web-common/src/features/i18n/project.inlang/.meta.json b/web-common/src/features/i18n/project.inlang/.meta.json new file mode 100644 index 000000000000..baea54877ce3 --- /dev/null +++ b/web-common/src/features/i18n/project.inlang/.meta.json @@ -0,0 +1,3 @@ +{ + "highestSdkVersion": "2.6.2" +} \ No newline at end of file diff --git a/web-common/src/features/i18n/project.inlang/README.md b/web-common/src/features/i18n/project.inlang/README.md new file mode 100644 index 000000000000..e8bd2f80610d --- /dev/null +++ b/web-common/src/features/i18n/project.inlang/README.md @@ -0,0 +1,103 @@ + +## What is this folder? + +This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) inlang project. + +## At a glance + +Purpose: +- This folder stores inlang project configuration and plugin cache data. +- Translation files live outside this folder and are referenced from `settings.json`. + +Safe to edit: +- `settings.json` + +Do not edit: +- `cache/` +- `.gitignore` + +Key files: +- `settings.json` — locales, plugins, file patterns (source of truth) +- `cache/` — plugin caches (safe to delete) +- `.gitignore` — generated + +``` +*.inlang/ +├── settings.json # Locales, plugins, and file patterns (source of truth) +├── cache/ # Plugin caches (gitignored) +└── .gitignore # Ignores everything except settings.json +``` + +Translation files (like `messages/en.json`) live **outside** this folder and are referenced via plugins in `settings.json`. + +## What is inlang? + +[Inlang](https://inlang.com) is an open file format for building custom localization (i18n) tooling. It provides: + +- **CRUD API** — Read and write translations programmatically via SQL +- **Plugin system** — Import/export any format (JSON, XLIFF, etc.) +- **Version control** — Built-in version control via [lix](https://lix.dev) + +``` +┌──────────┐ ┌───────────┐ ┌────────────┐ +│ i18n lib │ │Translation│ │ CI/CD │ +│ │ │ Tool │ │ Automation │ +└────┬─────┘ └─────┬─────┘ └─────┬──────┘ + │ │ │ + └─────────┐ │ ┌──────────┘ + ▼ ▼ ▼ + ┌──────────────────────────────────┐ + │ *.inlang file │ + └──────────────────────────────────┘ +``` + +## Quick start + +```bash +npm install @inlang/sdk +``` + +```ts +import { loadProjectFromDirectory, saveProjectToDirectory } from "@inlang/sdk"; + +const project = await loadProjectFromDirectory({ path: "./project.inlang" }); +// Query messages with SQLite + [Kysely](https://kysely.dev/) under the hood. +const messages = await project.db.selectFrom("message").selectAll().execute(); + +// Use project.db to update messages. +await saveProjectToDirectory({ path: "./project.inlang", project }); +``` + +## Ideas for custom tooling + +- Translation health dashboard (missing/empty/stale messages) +- Locale coverage report in CI +- Auto-PR for new keys with placeholders +- Migration tool between file formats via plugins +- Glossary/term consistency checker + +## Data model ([docs](https://inlang.com/docs/data-model)) + +``` +bundle (a concept, e.g., "welcome_header") + └── message (per locale, e.g., "en", "de") + └── variant (plural forms, gender, etc.) +``` + +- **bundle**: Groups messages by ID (e.g., `welcome_header`) +- **message**: A translation for a specific locale +- **variant**: Handles pluralization/selectors (most messages have one variant) + +## Common tasks + +- List bundles: `project.db.selectFrom("bundle").selectAll().execute()` +- List messages for locale: `project.db.selectFrom("message").where("locale", "=", "en").selectAll().execute()` +- Find missing translations: compare message counts across locales +- Update a message: `project.db.updateTable("message").set({ ... }).where("id", "=", "...").execute()` + +## Links + +- [SDK documentation](https://inlang.com/docs) +- [inlang.com](https://inlang.com) +- [List of plugins](https://inlang.com/c/plugins) +- [List of tools](https://inlang.com/c/tools) diff --git a/web-common/src/features/i18n/project.inlang/settings.json b/web-common/src/features/i18n/project.inlang/settings.json new file mode 100644 index 000000000000..9bdce4c8cc9b --- /dev/null +++ b/web-common/src/features/i18n/project.inlang/settings.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "baseLocale": "en", + "locales": ["en", "de"], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{locale}.json" + } +} diff --git a/web-common/src/layout/ApplicationHeader.svelte b/web-common/src/layout/ApplicationHeader.svelte index 10d3f2732ce8..072de842e971 100644 --- a/web-common/src/layout/ApplicationHeader.svelte +++ b/web-common/src/layout/ApplicationHeader.svelte @@ -30,6 +30,7 @@ import InputWithConfirm from "../components/forms/InputWithConfirm.svelte"; import Tag from "../components/tag/Tag.svelte"; import { fileArtifacts } from "../features/entity-management/file-artifacts"; + import { m } from "@rilldata/web-common/features/i18n/gen/messages"; const { deploy, developerChat, stickyDashboardState } = featureFlags; const runtimeClient = useRuntimeClient(); @@ -108,6 +109,8 @@ +
{m.example_message({ username: "world" })} - {m.example_title()}
+ {#if mode === "Preview" || onVizRoute} {#if $exploresQuery?.data} diff --git a/web-local/src/app.html b/web-local/src/app.html index 3274bf3fb9c9..40918cd5e081 100644 --- a/web-local/src/app.html +++ b/web-local/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/web-local/src/hooks.server.ts b/web-local/src/hooks.server.ts new file mode 100644 index 000000000000..541864bff2b1 --- /dev/null +++ b/web-local/src/hooks.server.ts @@ -0,0 +1,18 @@ +import type { Handle } from "@sveltejs/kit"; +import { paraglideMiddleware } from "@rilldata/web-common/features/i18n/gen/server"; + +// creating a handle to use the paraglide middleware +const paraglideHandle: Handle = ({ event, resolve }) => + paraglideMiddleware( + event.request, + ({ request: localizedRequest, locale }) => { + event.request = localizedRequest; + return resolve(event, { + transformPageChunk: ({ html }) => { + return html.replace("%lang%", locale); + }, + }); + }, + ); + +export const handle: Handle = paraglideHandle; diff --git a/web-local/src/hooks.ts b/web-local/src/hooks.ts new file mode 100644 index 000000000000..bf96600dde15 --- /dev/null +++ b/web-local/src/hooks.ts @@ -0,0 +1,6 @@ +import type { Reroute } from "@sveltejs/kit"; +import { deLocalizeUrl } from "@rilldata/web-common/features/i18n/gen/runtime"; + +export const reroute: Reroute = (request) => { + return deLocalizeUrl(request.url).pathname; +}; diff --git a/web-local/vite.config.ts b/web-local/vite.config.ts index 1adbbe781555..b45e3a60c263 100644 --- a/web-local/vite.config.ts +++ b/web-local/vite.config.ts @@ -1,6 +1,7 @@ import { sveltekit } from "@sveltejs/kit/vite"; import dns from "dns"; import { defineConfig } from "vitest/config"; +import { paraglideVitePlugin } from "@inlang/paraglide-js"; // print dev server as `localhost` not `127.0.0.1` dns.setDefaultResultOrder("verbatim"); @@ -46,7 +47,14 @@ const config = defineConfig({ "memoize-weak", ], }, - plugins: [sveltekit()], + plugins: [ + sveltekit(), + paraglideVitePlugin({ + project: "../web-common/src/features/i18n/project.inlang", + outdir: "./src/features/i18n", + strategy: ["url", "cookie", "baseLocale"], + }), + ], envDir: "../", envPrefix: "RILL_UI_PUBLIC_", }); From d7d35f9aeaac3bae229e438f51722dd6c6e06c2e Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Tue, 16 Jun 2026 18:58:20 +0530 Subject: [PATCH 2/8] Add to web-admin and i18n safeguard --- package.json | 4 +- scripts/i18n-guard.js | 109 ++++++++++++++++++ scripts/web-test-code-quality.sh | 5 + web-admin/src/app.html | 2 +- web-admin/src/hooks.server.ts | 18 +++ web-admin/vite.config.ts | 10 +- .../src/layout/ApplicationHeader.svelte | 3 - web-common/src/lib/i18n/README.md | 101 ++++++++++++++++ .../{features => lib}/i18n/messages/de.json | 0 .../{features => lib}/i18n/messages/en.json | 0 .../i18n/project.inlang/.gitignore | 0 .../i18n/project.inlang/.meta.json | 0 .../i18n/project.inlang/README.md | 0 .../i18n/project.inlang/settings.json | 0 web-local/src/hooks.server.ts | 2 +- web-local/src/hooks.ts | 2 +- web-local/vite.config.ts | 6 +- 17 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 scripts/i18n-guard.js create mode 100644 web-admin/src/hooks.server.ts create mode 100644 web-common/src/lib/i18n/README.md rename web-common/src/{features => lib}/i18n/messages/de.json (100%) rename web-common/src/{features => lib}/i18n/messages/en.json (100%) rename web-common/src/{features => lib}/i18n/project.inlang/.gitignore (100%) rename web-common/src/{features => lib}/i18n/project.inlang/.meta.json (100%) rename web-common/src/{features => lib}/i18n/project.inlang/README.md (100%) rename web-common/src/{features => lib}/i18n/project.inlang/settings.json (100%) diff --git a/package.json b/package.json index 102b7351791f..fc0cfa8c370e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "web-local" ], "scripts": { - "build:i18n": "paraglide-js compile --project web-common/src/features/i18n/project.inlang --outdir web-common/src/features/i18n/gen", + "build:i18n": "paraglide-js compile --project web-common/src/lib/i18n/project.inlang --outdir web-common/src/lib/i18n/gen", "build": "npm run build -w web-local", "dev": "sh -c 'npm run dev-runtime -- \"$@\" & npm run dev-web -- --port 3001 & wait' --", "dev-web": "npm run dev -w web-local -- ", @@ -23,7 +23,7 @@ "quality": "bash ./scripts/web-test-code-quality.sh", "quality:ci": "FAIL_FAST=true bash ./scripts/web-test-code-quality.sh", "check:edit-route-parity": "node ./scripts/check-edit-route-parity.js", - "machine-translate": "inlang machine translate --project web-common/src/features/i18n/project.inlang" + "machine-translate": "inlang machine translate --project web-common/src/lib/i18n/project.inlang" }, "devDependencies": { "@inlang/paraglide-js": "^2.10.0", diff --git a/scripts/i18n-guard.js b/scripts/i18n-guard.js new file mode 100644 index 000000000000..2eb468b1b07a --- /dev/null +++ b/scripts/i18n-guard.js @@ -0,0 +1,109 @@ +#!/usr/bin/env node +// Heuristic guard against hardcoded user-facing strings in already-migrated +// areas of the frontend. It scans `.svelte` markup (not + + +

{m.welcome_greeting({ name })}

+``` + +- Import the `m` namespace; call messages as functions. +- Pass interpolation values as a named object: `m.welcome_greeting({ name })`. +- Override the locale per call when needed: `m.common_cancel({}, { locale: "de" })`. + +Locale detection uses the `["preferredLanguage", "baseLocale"]` strategy: the +browser's preferred language, falling back to English. + +## Conventions + +### Key naming + +Use `feature_component_purpose`, lower snake_case, grouped by prefix in +`en.json`: + +```jsonc +{ + "common_cancel": "Cancel", + "common_save": "Save", + "welcome_greeting": "Welcome, {name}", + "dashboards_filters_clear_all": "Clear all filters" +} +``` + +- `common_` — copy reused across features. Reuse an existing `common_` key + rather than duplicating identical copy. +- Otherwise prefix with the feature directory name. + +### Interpolation + +Use named placeholders, never string concatenation: + +```jsonc +{ "exports_row_count": "Exporting {count} rows" } +``` + +### Pluralization and variants + +Use Paraglide [variants](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/variants) +rather than hand-rolled `count === 1 ? ... : ...` logic. + +## Adding or migrating a string + +1. Add the key to `messages/en.json` following the naming convention. +2. Run `npm run machine-translate` to populate other locales. +3. Replace the literal in code with `m.key()` (or `m.key({ var })`). +4. `npm run build:i18n` (or rely on the Vite plugin in dev). +5. Run `npm run test -w web-common` and `npm run quality`. + +## Guard against new hardcoded strings + +`scripts/i18n-guard.js` scans already-migrated areas for hardcoded +user-facing strings and runs in `npm run quality`. It is a heuristic and +currently **warning-only**; the final migration chunk flips it to `--strict` +(fatal). Each migration chunk appends its directories to `MIGRATED_GLOBS` in +that script. Suppress an intentional literal with an `i18n-ignore` comment on +the line or the line above it. diff --git a/web-common/src/features/i18n/messages/de.json b/web-common/src/lib/i18n/messages/de.json similarity index 100% rename from web-common/src/features/i18n/messages/de.json rename to web-common/src/lib/i18n/messages/de.json diff --git a/web-common/src/features/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json similarity index 100% rename from web-common/src/features/i18n/messages/en.json rename to web-common/src/lib/i18n/messages/en.json diff --git a/web-common/src/features/i18n/project.inlang/.gitignore b/web-common/src/lib/i18n/project.inlang/.gitignore similarity index 100% rename from web-common/src/features/i18n/project.inlang/.gitignore rename to web-common/src/lib/i18n/project.inlang/.gitignore diff --git a/web-common/src/features/i18n/project.inlang/.meta.json b/web-common/src/lib/i18n/project.inlang/.meta.json similarity index 100% rename from web-common/src/features/i18n/project.inlang/.meta.json rename to web-common/src/lib/i18n/project.inlang/.meta.json diff --git a/web-common/src/features/i18n/project.inlang/README.md b/web-common/src/lib/i18n/project.inlang/README.md similarity index 100% rename from web-common/src/features/i18n/project.inlang/README.md rename to web-common/src/lib/i18n/project.inlang/README.md diff --git a/web-common/src/features/i18n/project.inlang/settings.json b/web-common/src/lib/i18n/project.inlang/settings.json similarity index 100% rename from web-common/src/features/i18n/project.inlang/settings.json rename to web-common/src/lib/i18n/project.inlang/settings.json diff --git a/web-local/src/hooks.server.ts b/web-local/src/hooks.server.ts index 541864bff2b1..05424484474c 100644 --- a/web-local/src/hooks.server.ts +++ b/web-local/src/hooks.server.ts @@ -1,5 +1,5 @@ import type { Handle } from "@sveltejs/kit"; -import { paraglideMiddleware } from "@rilldata/web-common/features/i18n/gen/server"; +import { paraglideMiddleware } from "@rilldata/web-common/lib/i18n/gen/server"; // creating a handle to use the paraglide middleware const paraglideHandle: Handle = ({ event, resolve }) => diff --git a/web-local/src/hooks.ts b/web-local/src/hooks.ts index bf96600dde15..32692100fb4a 100644 --- a/web-local/src/hooks.ts +++ b/web-local/src/hooks.ts @@ -1,5 +1,5 @@ import type { Reroute } from "@sveltejs/kit"; -import { deLocalizeUrl } from "@rilldata/web-common/features/i18n/gen/runtime"; +import { deLocalizeUrl } from "@rilldata/web-common/lib/i18n/gen/runtime"; export const reroute: Reroute = (request) => { return deLocalizeUrl(request.url).pathname; diff --git a/web-local/vite.config.ts b/web-local/vite.config.ts index b45e3a60c263..0e3ae84fe33a 100644 --- a/web-local/vite.config.ts +++ b/web-local/vite.config.ts @@ -50,9 +50,9 @@ const config = defineConfig({ plugins: [ sveltekit(), paraglideVitePlugin({ - project: "../web-common/src/features/i18n/project.inlang", - outdir: "./src/features/i18n", - strategy: ["url", "cookie", "baseLocale"], + project: "../web-common/src/lib/i18n/project.inlang", + outdir: "../web-common/src/lib/i18n/gen", + strategy: ["preferredLanguage", "baseLocale"], }), ], envDir: "../", From 187efe8b5d0e8d1ea17995cb32ebb6aaf90e7808 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 17 Jun 2026 10:21:34 +0530 Subject: [PATCH 3/8] Move basic org and project pages --- scripts/i18n-guard.js | 16 +++++++ .../dashboards/listing/DashboardsTable.svelte | 10 ++-- .../DashboardsTableCompositeCell.svelte | 8 +++- .../listing/LastRefreshedDate.svelte | 3 +- .../features/projects/ProjectBuilding.svelte | 5 +- .../src/features/projects/ProjectCard.svelte | 17 ++++--- .../projects/ProjectCardActions.svelte | 13 ++++-- .../src/features/projects/ProjectCards.svelte | 7 +-- .../src/features/projects/ProjectTabs.svelte | 17 +++---- .../projects/RedeployProjectCTA.svelte | 19 +++++--- .../src/routes/[organization]/+page.svelte | 3 +- .../[organization]/[project]/+layout.svelte | 7 +-- .../[organization]/[project]/+page.svelte | 23 ++++++---- web-common/src/lib/i18n/messages/de.json | 46 ++++++++++++++++++- web-common/src/lib/i18n/messages/en.json | 46 ++++++++++++++++++- 15 files changed, 185 insertions(+), 55 deletions(-) diff --git a/scripts/i18n-guard.js b/scripts/i18n-guard.js index 2eb468b1b07a..7734a380d6a8 100644 --- a/scripts/i18n-guard.js +++ b/scripts/i18n-guard.js @@ -24,6 +24,22 @@ const MIGRATED_GLOBS = [ "web-common/src/features/welcome/**/*.svelte", "web-common/src/features/onboarding/**/*.svelte", "web-common/src/features/help/**/*.svelte", + // web-admin organization overview page (chunk A). Literal `[organization]` + // brackets are a glob character class, so match the segment with a wildcard. + "web-admin/src/routes/*organization*/+page.svelte", + "web-admin/src/features/organizations/OrganizationHero.svelte", + "web-admin/src/features/projects/ProjectCards.svelte", + "web-admin/src/features/projects/ProjectCard.svelte", + "web-admin/src/features/projects/ProjectCardActions.svelte", + // web-admin project overview page (chunk B). + "web-admin/src/routes/*organization*/*project*/+page.svelte", + "web-admin/src/routes/*organization*/*project*/+layout.svelte", + "web-admin/src/features/projects/ProjectTabs.svelte", + "web-admin/src/features/projects/ProjectBuilding.svelte", + "web-admin/src/features/projects/RedeployProjectCTA.svelte", + "web-admin/src/features/dashboards/listing/DashboardsTable.svelte", + "web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte", + "web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte", ]; // Human-facing attributes worth translating. Attributes like `class`, `id`, diff --git a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte index 53b0c877b4d6..7e57f29caabb 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte @@ -4,6 +4,7 @@ import ResourceList from "@rilldata/web-admin/features/resources/ResourceList.svelte"; import ResourceListEmptyState from "@rilldata/web-admin/features/resources/ResourceListEmptyState.svelte"; import ExploreIcon from "@rilldata/web-common/components/icons/ExploreIcon.svelte"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import DelayedSpinner from "@rilldata/web-common/features/entity-management/DelayedSpinner.svelte"; import type { V1Resource } from "@rilldata/web-common/runtime-client"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; @@ -140,7 +141,7 @@ - Create a dashboard to get started + {m.dashboards_listing_empty_create_link()} + {m.dashboards_listing_empty_create_suffix()} @@ -159,7 +161,7 @@ href={`/${organization}/${project}/-/dashboards`} class="text-sm font-medium text-primary-600 hover:text-primary-700 transition-colors inline-block" > - See all dashboards → + {m.dashboards_listing_see_all()} → {/if} diff --git a/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte b/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte index 41b378e308c3..f09c43254216 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte @@ -4,6 +4,7 @@ import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import ResourceTypeBadge from "@rilldata/web-common/features/entity-management/ResourceTypeBadge.svelte"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { timeAgo } from "@rilldata/web-common/lib/time/relative-time"; export let name: string; @@ -37,7 +38,7 @@ {title !== "" ? title : name} {#if error !== ""} - Error + {m.common_error()} {/if}
- Last refreshed {timeAgo(lastRefreshedDate)}{m.dashboards_listing_last_refreshed({ + time: timeAgo(lastRefreshedDate), + })} {lastRefreshedDate.toLocaleString()} diff --git a/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte b/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte index 5d1375ec5151..86ffaa1de7cb 100644 --- a/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte +++ b/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte @@ -3,6 +3,7 @@ import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import { createRuntimeServiceGetExplore } from "@rilldata/web-common/runtime-client"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { timeAgo } from "@rilldata/web-common/lib/time/relative-time"; export let dashboard: string; @@ -28,7 +29,7 @@ {#if data}
- Last refreshed {timeAgo(data)} + {m.dashboards_listing_last_refreshed({ time: timeAgo(data) })}
{data.toLocaleString()} diff --git a/web-admin/src/features/projects/ProjectBuilding.svelte b/web-admin/src/features/projects/ProjectBuilding.svelte index fb55516c402f..569de68f5a61 100644 --- a/web-admin/src/features/projects/ProjectBuilding.svelte +++ b/web-admin/src/features/projects/ProjectBuilding.svelte @@ -7,6 +7,7 @@ import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; import { isEmbedPage } from "@rilldata/web-common/layout/navigation/navigation-utils.ts"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; export let branch: string | undefined = undefined; @@ -20,9 +21,9 @@
{#if branch} - Starting branch deployment... + {m.projects_building_branch()} {:else} - Hang tight! We're deploying your project... + {m.projects_building_default()} {/if} {#if !onEmbedPage} diff --git a/web-admin/src/features/projects/ProjectCard.svelte b/web-admin/src/features/projects/ProjectCard.svelte index be0df9e94a8c..e358e7462be2 100644 --- a/web-admin/src/features/projects/ProjectCard.svelte +++ b/web-admin/src/features/projects/ProjectCard.svelte @@ -5,6 +5,7 @@ import Tag from "@rilldata/web-common/components/tag/Tag.svelte"; import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte"; import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { createAdminServiceGetProject } from "../../client"; import ProjectAccessControls from "./ProjectAccessControls.svelte"; import ProjectCardActions from "@rilldata/web-admin/features/projects/ProjectCardActions.svelte"; @@ -58,8 +59,12 @@ - Viewer - Admin + {m.common_viewer()} + {m.common_admin()} @@ -71,12 +76,12 @@ {/if} - This project is + + {m.projects_card_visibility_prefix()} {#if $proj.data.project.public} - public + {m.common_public()} {:else} - private + {m.common_private()} {/if} diff --git a/web-admin/src/features/projects/ProjectCardActions.svelte b/web-admin/src/features/projects/ProjectCardActions.svelte index 31b6951b0f95..48186835206e 100644 --- a/web-admin/src/features/projects/ProjectCardActions.svelte +++ b/web-admin/src/features/projects/ProjectCardActions.svelte @@ -4,6 +4,7 @@ import FeatherEditIcon from "@rilldata/web-common/components/icons/FeatherEditIcon.svelte"; import PencilIcon from "@rilldata/web-common/components/icons/PencilIcon.svelte"; import Trash from "@rilldata/web-common/components/icons/Trash.svelte"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { ShareIcon } from "lucide-svelte"; let { @@ -29,19 +30,23 @@ - Edit + + {m.common_edit()} - Rename + + {m.common_rename()} - Share + + {m.common_share()} - Delete + + {m.common_delete()} diff --git a/web-admin/src/features/projects/ProjectCards.svelte b/web-admin/src/features/projects/ProjectCards.svelte index 8355bc9b94a1..54ac8390c996 100644 --- a/web-admin/src/features/projects/ProjectCards.svelte +++ b/web-admin/src/features/projects/ProjectCards.svelte @@ -1,5 +1,6 @@ - {title} overview - Rill + {m.organizations_overview_page_title({ title })} diff --git a/web-admin/src/routes/[organization]/[project]/+layout.svelte b/web-admin/src/routes/[organization]/[project]/+layout.svelte index 06ed79238b1a..6178385a7482 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/+layout.svelte @@ -49,6 +49,7 @@ import { getThemedLogoUrl } from "@rilldata/web-admin/features/themes/organization-logo"; import { viewAsUserStore } from "@rilldata/web-admin/features/view-as-user/viewAsUserStore"; import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { themeControl } from "@rilldata/web-common/features/themes/theme-control"; import { metricsService } from "@rilldata/web-common/metrics/initMetrics"; import RuntimeProvider from "@rilldata/web-common/runtime-client/v2/RuntimeProvider.svelte"; @@ -257,7 +258,7 @@ /> {:else if projectData} @@ -329,10 +330,10 @@ {:else if deploymentStatus === V1DeploymentStatus.DEPLOYMENT_STATUS_ERRORED} {:else if deploymentStatus === V1DeploymentStatus.DEPLOYMENT_STATUS_STOPPED || deploymentStatus === V1DeploymentStatus.DEPLOYMENT_STATUS_STOPPING} - {projectDisplayName} - Rill + {m.projects_overview_page_title({ name: projectDisplayName })} @@ -40,25 +41,25 @@ {:else if isErrorDisplayName}

- Welcome to {project} + {m.projects_overview_welcome()} + {project}

{:else}

- Welcome to {projectDisplayName} + {m.projects_overview_welcome()} + {projectDisplayName}

{/if}

{#if $chat} - Ask questions about your data, or explore your dashboards below + {m.projects_overview_subtitle_chat()} {:else} - Explore your dashboards below + {m.projects_overview_subtitle()} {/if}

@@ -73,7 +74,9 @@
-

Dashboards

+

+ {m.common_dashboards()} +

diff --git a/web-common/src/lib/i18n/messages/de.json b/web-common/src/lib/i18n/messages/de.json index 4e0623bd2d5f..3e37d7a6e286 100644 --- a/web-common/src/lib/i18n/messages/de.json +++ b/web-common/src/lib/i18n/messages/de.json @@ -1,5 +1,47 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", - "example_message": "Guten Tag {username}", - "example_title": "Rill Projekt" + "common_edit": "Bearbeiten", + "common_rename": "Umbenennen", + "common_share": "Teilen", + "common_delete": "Löschen", + "common_viewer": "Betrachter", + "common_admin": "Administrator", + "common_public": "öffentlich", + "common_private": "privat", + "common_home": "Startseite", + "common_ai": "KI", + "common_dashboards": "Dashboards", + "common_query": "Abfrage", + "common_reports": "Berichte", + "common_alerts": "Warnungen", + "common_status": "Status", + "common_settings": "Einstellungen", + "common_error": "Fehler", + "organizations_overview_page_title": "{title} Übersicht - Rill", + "projects_cards_subtitle": "Sehen Sie sich unten Ihre Projekte an.", + "projects_cards_new_project": "+ Neues Projekt", + "projects_cards_empty": "Diese Organisation hat noch keine Projekte.", + "projects_card_visibility_prefix": "Dieses Projekt ist", + "projects_overview_page_title": "{name} - Rill", + "projects_overview_title_aria": "Projekttitel", + "projects_overview_welcome": "Willkommen bei", + "projects_overview_subtitle_chat": "Stellen Sie Fragen zu Ihren Daten oder erkunden Sie unten Ihre Dashboards", + "projects_overview_subtitle": "Erkunden Sie unten Ihre Dashboards", + "projects_layout_error_fetching_deployment": "Fehler beim Abrufen des Deployments", + "projects_layout_deployment_error_header": "Deployment-Fehler", + "projects_layout_deployment_error_body": "Beim Deployen Ihres Projekts ist ein Fehler aufgetreten. Bitte kontaktieren Sie den Support.", + "projects_building_branch": "Branch-Deployment wird gestartet...", + "projects_building_default": "Einen Moment! Wir deployen Ihr Projekt...", + "projects_redeploy_waking": "Ihr Projekt wird aufgeweckt...", + "projects_redeploy_hibernating": "Ihr Projekt befindet sich im Ruhezustand", + "projects_redeploy_wake_button": "Projekt aufwecken", + "projects_redeploy_waking_button": "Wird aufgeweckt...", + "projects_redeploy_hibernating_readonly": "Dieses Projekt befindet sich im Ruhezustand", + "projects_redeploy_contact_admin": "Wenden Sie sich an den Administrator des Projekts, um das Projekt erneut zu deployen.", + "projects_redeploy_wake_failed": "Projekt konnte nicht aufgeweckt werden: {error}", + "dashboards_listing_empty_title": "Sie haben noch keine Dashboards", + "dashboards_listing_empty_create_link": "Erstellen Sie ein Dashboard", + "dashboards_listing_empty_create_suffix": "um loszulegen", + "dashboards_listing_see_all": "Alle Dashboards anzeigen", + "dashboards_listing_last_refreshed": "Zuletzt aktualisiert {time}" } diff --git a/web-common/src/lib/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json index 2162954813df..bf2989a2ddf7 100644 --- a/web-common/src/lib/i18n/messages/en.json +++ b/web-common/src/lib/i18n/messages/en.json @@ -1,5 +1,47 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", - "example_message": "Hello world {username}", - "example_title": "Rill project" + "common_edit": "Edit", + "common_rename": "Rename", + "common_share": "Share", + "common_delete": "Delete", + "common_viewer": "Viewer", + "common_admin": "Admin", + "common_public": "public", + "common_private": "private", + "common_home": "Home", + "common_ai": "AI", + "common_dashboards": "Dashboards", + "common_query": "Query", + "common_reports": "Reports", + "common_alerts": "Alerts", + "common_status": "Status", + "common_settings": "Settings", + "common_error": "Error", + "organizations_overview_page_title": "{title} overview - Rill", + "projects_cards_subtitle": "Check out your projects below.", + "projects_cards_new_project": "+ New project", + "projects_cards_empty": "This organization has no projects yet.", + "projects_card_visibility_prefix": "This project is", + "projects_overview_page_title": "{name} - Rill", + "projects_overview_title_aria": "Project title", + "projects_overview_welcome": "Welcome to", + "projects_overview_subtitle_chat": "Ask questions about your data, or explore your dashboards below", + "projects_overview_subtitle": "Explore your dashboards below", + "projects_layout_error_fetching_deployment": "Error fetching deployment", + "projects_layout_deployment_error_header": "Deployment Error", + "projects_layout_deployment_error_body": "There was an error deploying your project. Please contact support.", + "projects_building_branch": "Starting branch deployment...", + "projects_building_default": "Hang tight! We're deploying your project...", + "projects_redeploy_waking": "Waking up your project...", + "projects_redeploy_hibernating": "Your project is hibernating", + "projects_redeploy_wake_button": "Wake project", + "projects_redeploy_waking_button": "Waking...", + "projects_redeploy_hibernating_readonly": "This project is hibernating", + "projects_redeploy_contact_admin": "Contact the project's administrator to redeploy the project.", + "projects_redeploy_wake_failed": "Failed to wake project: {error}", + "dashboards_listing_empty_title": "You don't have any dashboards yet", + "dashboards_listing_empty_create_link": "Create a dashboard", + "dashboards_listing_empty_create_suffix": "to get started", + "dashboards_listing_see_all": "See all dashboards", + "dashboards_listing_last_refreshed": "Last refreshed {time}" } From f53f67b1f33f92847a942657748fc8e25496d6da Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Fri, 19 Jun 2026 12:48:59 +0530 Subject: [PATCH 4/8] Add a selector in user section --- package.json | 6 ++-- scripts/i18n-guard.js | 1 + .../authentication/AvatarButton.svelte | 3 ++ .../authentication/LanguageSelector.svelte | 32 +++++++++++++++++++ .../organizations/OrganizationTabs.svelte | 7 ++-- web-admin/vite.config.ts | 2 +- web-common/src/lib/i18n/messages/de.json | 2 ++ web-common/src/lib/i18n/messages/en.json | 2 ++ 8 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 web-admin/src/features/authentication/LanguageSelector.svelte diff --git a/package.json b/package.json index fc0cfa8c370e..6fad56a6ad99 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "gen:colors": "node web-common/src/features/themes/gen-colors.ts", "quality": "bash ./scripts/web-test-code-quality.sh", "quality:ci": "FAIL_FAST=true bash ./scripts/web-test-code-quality.sh", - "check:edit-route-parity": "node ./scripts/check-edit-route-parity.js", - "machine-translate": "inlang machine translate --project web-common/src/lib/i18n/project.inlang" + "check:edit-route-parity": "node ./scripts/check-edit-route-parity.js" }, "devDependencies": { "@inlang/paraglide-js": "^2.10.0", @@ -40,8 +39,7 @@ "sass": "^1.88.0", "syncpack": "^13.0.0", "typescript": "^5.6.2", - "typescript-eslint": "^8.30.1", - "@inlang/cli": "^3.0.0" + "typescript-eslint": "^8.30.1" }, "lint-staged": { "*.{svelte,ts}": [ diff --git a/scripts/i18n-guard.js b/scripts/i18n-guard.js index 7734a380d6a8..10162ff8e27e 100644 --- a/scripts/i18n-guard.js +++ b/scripts/i18n-guard.js @@ -28,6 +28,7 @@ const MIGRATED_GLOBS = [ // brackets are a glob character class, so match the segment with a wildcard. "web-admin/src/routes/*organization*/+page.svelte", "web-admin/src/features/organizations/OrganizationHero.svelte", + "web-admin/src/features/organizations/OrganizationTabs.svelte", "web-admin/src/features/projects/ProjectCards.svelte", "web-admin/src/features/projects/ProjectCard.svelte", "web-admin/src/features/projects/ProjectCardActions.svelte", diff --git a/web-admin/src/features/authentication/AvatarButton.svelte b/web-admin/src/features/authentication/AvatarButton.svelte index 10998e78492c..c29dd0e480a4 100644 --- a/web-admin/src/features/authentication/AvatarButton.svelte +++ b/web-admin/src/features/authentication/AvatarButton.svelte @@ -27,6 +27,7 @@ } from "../../client"; import ViewAsUserPopover from "../view-as-user/ViewAsUserPopover.svelte"; import ThemeToggle from "@rilldata/web-common/features/themes/ThemeToggle.svelte"; + import LanguageSelector from "./LanguageSelector.svelte"; export let projectPermissions: V1ProjectPermissions | undefined = undefined; @@ -124,6 +125,8 @@ {/if} + + + import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; + import { + getLocale, + locales, + setLocale, + type Locale, + } from "@rilldata/web-common/lib/i18n/gen/runtime"; + + const localeLabels: Record = { + en: "English", + de: "Deutsch", + }; + + // setLocale reloads the page, so reading once on mount is sufficient. + const currentLocale = getLocale(); + + + + Language + + {#each locales as locale (locale)} + setLocale(locale)} + > + {localeLabels[locale] ?? locale} + + {/each} + + diff --git a/web-admin/src/features/organizations/OrganizationTabs.svelte b/web-admin/src/features/organizations/OrganizationTabs.svelte index 1de16f5dfe11..3b3cb1eefd25 100644 --- a/web-admin/src/features/organizations/OrganizationTabs.svelte +++ b/web-admin/src/features/organizations/OrganizationTabs.svelte @@ -5,6 +5,7 @@ width, position, } from "@rilldata/web-admin//components/nav/Tab.svelte"; + import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; export let organization: string; export let organizationPermissions: V1OrganizationPermissions; @@ -13,17 +14,17 @@ $: tabs = [ { route: `/${organization}`, - label: "Projects", + label: m.common_projects(), hasPermission: true, }, { route: `/${organization}/-/users`, - label: "Users", + label: m.common_users(), hasPermission: organizationPermissions.manageOrgMembers, }, { route: `/${organization}/-/settings`, - label: "Settings", + label: m.common_settings(), hasPermission: organizationPermissions.manageOrg, }, ]; diff --git a/web-admin/vite.config.ts b/web-admin/vite.config.ts index 20831b0d41f6..c010847ee0b9 100644 --- a/web-admin/vite.config.ts +++ b/web-admin/vite.config.ts @@ -45,7 +45,7 @@ export default defineConfig({ paraglideVitePlugin({ project: "../web-common/src/lib/i18n/project.inlang", outdir: "../web-common/src/lib/i18n/gen", - strategy: ["preferredLanguage", "baseLocale"], + strategy: ["localStorage", "preferredLanguage", "baseLocale"], }), ], envDir: "../", diff --git a/web-common/src/lib/i18n/messages/de.json b/web-common/src/lib/i18n/messages/de.json index 3e37d7a6e286..a47b85a1be61 100644 --- a/web-common/src/lib/i18n/messages/de.json +++ b/web-common/src/lib/i18n/messages/de.json @@ -16,6 +16,8 @@ "common_alerts": "Warnungen", "common_status": "Status", "common_settings": "Einstellungen", + "common_projects": "Projekte", + "common_users": "Benutzer", "common_error": "Fehler", "organizations_overview_page_title": "{title} Übersicht - Rill", "projects_cards_subtitle": "Sehen Sie sich unten Ihre Projekte an.", diff --git a/web-common/src/lib/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json index bf2989a2ddf7..eb78b93665e0 100644 --- a/web-common/src/lib/i18n/messages/en.json +++ b/web-common/src/lib/i18n/messages/en.json @@ -16,6 +16,8 @@ "common_alerts": "Alerts", "common_status": "Status", "common_settings": "Settings", + "common_projects": "Projects", + "common_users": "Users", "common_error": "Error", "organizations_overview_page_title": "{title} overview - Rill", "projects_cards_subtitle": "Check out your projects below.", From c1bc287c3bce157c511da3a1a9cbe2b6710645d5 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Mon, 29 Jun 2026 16:57:53 +0530 Subject: [PATCH 5/8] Revert all text changes in this PR --- package.json | 2 +- scripts/i18n-guard.js | 22 +-------- .../authentication/AvatarButton.svelte | 3 -- .../authentication/LanguageSelector.svelte | 32 ------------ .../dashboards/listing/DashboardsTable.svelte | 10 ++-- .../DashboardsTableCompositeCell.svelte | 8 +-- .../listing/LastRefreshedDate.svelte | 3 +- .../organizations/OrganizationTabs.svelte | 7 ++- .../features/projects/ProjectBuilding.svelte | 5 +- .../src/features/projects/ProjectCard.svelte | 17 +++---- .../projects/ProjectCardActions.svelte | 13 ++--- .../src/features/projects/ProjectCards.svelte | 7 ++- .../src/features/projects/ProjectTabs.svelte | 17 +++---- .../projects/RedeployProjectCTA.svelte | 19 +++---- .../[organization]/[project]/+layout.svelte | 7 ++- .../[organization]/[project]/+page.svelte | 21 ++++---- web-common/src/lib/i18n/messages/de.json | 49 ------------------- web-common/src/lib/i18n/messages/en.json | 47 +----------------- .../src/lib/i18n/project.inlang/settings.json | 2 +- 19 files changed, 58 insertions(+), 233 deletions(-) delete mode 100644 web-admin/src/features/authentication/LanguageSelector.svelte delete mode 100644 web-common/src/lib/i18n/messages/de.json diff --git a/package.json b/package.json index 6fad56a6ad99..1c21c92dbc3e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build:i18n": "paraglide-js compile --project web-common/src/lib/i18n/project.inlang --outdir web-common/src/lib/i18n/gen", - "build": "npm run build -w web-local", + "build": "npm run build:i18n & npm run build -w web-local", "dev": "sh -c 'npm run dev-runtime -- \"$@\" & npm run dev-web -- --port 3001 & wait' --", "dev-web": "npm run dev -w web-local -- ", "dev-runtime": "node scripts/dev.js", diff --git a/scripts/i18n-guard.js b/scripts/i18n-guard.js index 10162ff8e27e..6d6e8ed757c1 100644 --- a/scripts/i18n-guard.js +++ b/scripts/i18n-guard.js @@ -20,27 +20,9 @@ import { relative } from "node:path"; // chunk appends its directories here; the guard only polices these areas so // unmigrated code does not drown the signal. const MIGRATED_GLOBS = [ - "web-common/src/layout/**/*.svelte", - "web-common/src/features/welcome/**/*.svelte", - "web-common/src/features/onboarding/**/*.svelte", - "web-common/src/features/help/**/*.svelte", - // web-admin organization overview page (chunk A). Literal `[organization]` - // brackets are a glob character class, so match the segment with a wildcard. + // web-admin organization overview page. Literal `[organization]` brackets are + // a glob character class, so match the segment with a wildcard. "web-admin/src/routes/*organization*/+page.svelte", - "web-admin/src/features/organizations/OrganizationHero.svelte", - "web-admin/src/features/organizations/OrganizationTabs.svelte", - "web-admin/src/features/projects/ProjectCards.svelte", - "web-admin/src/features/projects/ProjectCard.svelte", - "web-admin/src/features/projects/ProjectCardActions.svelte", - // web-admin project overview page (chunk B). - "web-admin/src/routes/*organization*/*project*/+page.svelte", - "web-admin/src/routes/*organization*/*project*/+layout.svelte", - "web-admin/src/features/projects/ProjectTabs.svelte", - "web-admin/src/features/projects/ProjectBuilding.svelte", - "web-admin/src/features/projects/RedeployProjectCTA.svelte", - "web-admin/src/features/dashboards/listing/DashboardsTable.svelte", - "web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte", - "web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte", ]; // Human-facing attributes worth translating. Attributes like `class`, `id`, diff --git a/web-admin/src/features/authentication/AvatarButton.svelte b/web-admin/src/features/authentication/AvatarButton.svelte index c29dd0e480a4..10998e78492c 100644 --- a/web-admin/src/features/authentication/AvatarButton.svelte +++ b/web-admin/src/features/authentication/AvatarButton.svelte @@ -27,7 +27,6 @@ } from "../../client"; import ViewAsUserPopover from "../view-as-user/ViewAsUserPopover.svelte"; import ThemeToggle from "@rilldata/web-common/features/themes/ThemeToggle.svelte"; - import LanguageSelector from "./LanguageSelector.svelte"; export let projectPermissions: V1ProjectPermissions | undefined = undefined; @@ -125,8 +124,6 @@ {/if} - - - import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu"; - import { - getLocale, - locales, - setLocale, - type Locale, - } from "@rilldata/web-common/lib/i18n/gen/runtime"; - - const localeLabels: Record = { - en: "English", - de: "Deutsch", - }; - - // setLocale reloads the page, so reading once on mount is sufficient. - const currentLocale = getLocale(); - - - - Language - - {#each locales as locale (locale)} - setLocale(locale)} - > - {localeLabels[locale] ?? locale} - - {/each} - - diff --git a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte index 7e57f29caabb..53b0c877b4d6 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTable.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTable.svelte @@ -4,7 +4,6 @@ import ResourceList from "@rilldata/web-admin/features/resources/ResourceList.svelte"; import ResourceListEmptyState from "@rilldata/web-admin/features/resources/ResourceListEmptyState.svelte"; import ExploreIcon from "@rilldata/web-common/components/icons/ExploreIcon.svelte"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import DelayedSpinner from "@rilldata/web-common/features/entity-management/DelayedSpinner.svelte"; import type { V1Resource } from "@rilldata/web-common/runtime-client"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; @@ -141,7 +140,7 @@ - {m.dashboards_listing_empty_create_link()} - {m.dashboards_listing_empty_create_suffix()} + Create a dashboard to get started @@ -161,7 +159,7 @@ href={`/${organization}/${project}/-/dashboards`} class="text-sm font-medium text-primary-600 hover:text-primary-700 transition-colors inline-block" > - {m.dashboards_listing_see_all()} → + See all dashboards → {/if} diff --git a/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte b/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte index f09c43254216..41b378e308c3 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTableCompositeCell.svelte @@ -4,7 +4,6 @@ import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import ResourceTypeBadge from "@rilldata/web-common/features/entity-management/ResourceTypeBadge.svelte"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { timeAgo } from "@rilldata/web-common/lib/time/relative-time"; export let name: string; @@ -38,7 +37,7 @@ {title !== "" ? title : name} {#if error !== ""} - {m.common_error()} + Error {/if}
- {m.dashboards_listing_last_refreshed({ - time: timeAgo(lastRefreshedDate), - })}Last refreshed {timeAgo(lastRefreshedDate)} {lastRefreshedDate.toLocaleString()} diff --git a/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte b/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte index 86ffaa1de7cb..5d1375ec5151 100644 --- a/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte +++ b/web-admin/src/features/dashboards/listing/LastRefreshedDate.svelte @@ -3,7 +3,6 @@ import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; import { createRuntimeServiceGetExplore } from "@rilldata/web-common/runtime-client"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { timeAgo } from "@rilldata/web-common/lib/time/relative-time"; export let dashboard: string; @@ -29,7 +28,7 @@ {#if data}
- {m.dashboards_listing_last_refreshed({ time: timeAgo(data) })} + Last refreshed {timeAgo(data)}
{data.toLocaleString()} diff --git a/web-admin/src/features/organizations/OrganizationTabs.svelte b/web-admin/src/features/organizations/OrganizationTabs.svelte index 3b3cb1eefd25..1de16f5dfe11 100644 --- a/web-admin/src/features/organizations/OrganizationTabs.svelte +++ b/web-admin/src/features/organizations/OrganizationTabs.svelte @@ -5,7 +5,6 @@ width, position, } from "@rilldata/web-admin//components/nav/Tab.svelte"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; export let organization: string; export let organizationPermissions: V1OrganizationPermissions; @@ -14,17 +13,17 @@ $: tabs = [ { route: `/${organization}`, - label: m.common_projects(), + label: "Projects", hasPermission: true, }, { route: `/${organization}/-/users`, - label: m.common_users(), + label: "Users", hasPermission: organizationPermissions.manageOrgMembers, }, { route: `/${organization}/-/settings`, - label: m.common_settings(), + label: "Settings", hasPermission: organizationPermissions.manageOrg, }, ]; diff --git a/web-admin/src/features/projects/ProjectBuilding.svelte b/web-admin/src/features/projects/ProjectBuilding.svelte index 569de68f5a61..fb55516c402f 100644 --- a/web-admin/src/features/projects/ProjectBuilding.svelte +++ b/web-admin/src/features/projects/ProjectBuilding.svelte @@ -7,7 +7,6 @@ import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; import { isEmbedPage } from "@rilldata/web-common/layout/navigation/navigation-utils.ts"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; export let branch: string | undefined = undefined; @@ -21,9 +20,9 @@
{#if branch} - {m.projects_building_branch()} + Starting branch deployment... {:else} - {m.projects_building_default()} + Hang tight! We're deploying your project... {/if} {#if !onEmbedPage} diff --git a/web-admin/src/features/projects/ProjectCard.svelte b/web-admin/src/features/projects/ProjectCard.svelte index e358e7462be2..be0df9e94a8c 100644 --- a/web-admin/src/features/projects/ProjectCard.svelte +++ b/web-admin/src/features/projects/ProjectCard.svelte @@ -5,7 +5,6 @@ import Tag from "@rilldata/web-common/components/tag/Tag.svelte"; import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte"; import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { createAdminServiceGetProject } from "../../client"; import ProjectAccessControls from "./ProjectAccessControls.svelte"; import ProjectCardActions from "@rilldata/web-admin/features/projects/ProjectCardActions.svelte"; @@ -59,12 +58,8 @@ - {m.common_viewer()} - {m.common_admin()} + Viewer + Admin @@ -76,12 +71,12 @@ {/if} - - {m.projects_card_visibility_prefix()} + This project is {#if $proj.data.project.public} - {m.common_public()} + public {:else} - {m.common_private()} + private {/if} diff --git a/web-admin/src/features/projects/ProjectCardActions.svelte b/web-admin/src/features/projects/ProjectCardActions.svelte index 48186835206e..31b6951b0f95 100644 --- a/web-admin/src/features/projects/ProjectCardActions.svelte +++ b/web-admin/src/features/projects/ProjectCardActions.svelte @@ -4,7 +4,6 @@ import FeatherEditIcon from "@rilldata/web-common/components/icons/FeatherEditIcon.svelte"; import PencilIcon from "@rilldata/web-common/components/icons/PencilIcon.svelte"; import Trash from "@rilldata/web-common/components/icons/Trash.svelte"; - import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; import { ShareIcon } from "lucide-svelte"; let { @@ -30,23 +29,19 @@ - - {m.common_edit()} + Edit - - {m.common_rename()} + Rename - - {m.common_share()} + Share - - {m.common_delete()} + Delete diff --git a/web-admin/src/features/projects/ProjectCards.svelte b/web-admin/src/features/projects/ProjectCards.svelte index 54ac8390c996..8355bc9b94a1 100644 --- a/web-admin/src/features/projects/ProjectCards.svelte +++ b/web-admin/src/features/projects/ProjectCards.svelte @@ -1,6 +1,5 @@ - {m.projects_overview_page_title({ name: projectDisplayName })} + {projectDisplayName} - Rill @@ -55,25 +54,25 @@ {:else if isErrorDisplayName}

- {m.projects_overview_welcome()} - {project} + Welcome to {project}

{:else}

- {m.projects_overview_welcome()} - {projectDisplayName} + Welcome to {projectDisplayName}

{/if}

{#if $chat} - {m.projects_overview_subtitle_chat()} + Ask questions about your data, or explore your dashboards below {:else} - {m.projects_overview_subtitle()} + Explore your dashboards below {/if}

@@ -93,7 +92,7 @@

- {m.common_dashboards()} + Dashboards {#if $personalCanvases && hasNoPersonalCanvases} {/if} diff --git a/web-common/src/lib/i18n/messages/de.json b/web-common/src/lib/i18n/messages/de.json deleted file mode 100644 index a47b85a1be61..000000000000 --- a/web-common/src/lib/i18n/messages/de.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "https://inlang.com/schema/inlang-message-format", - "common_edit": "Bearbeiten", - "common_rename": "Umbenennen", - "common_share": "Teilen", - "common_delete": "Löschen", - "common_viewer": "Betrachter", - "common_admin": "Administrator", - "common_public": "öffentlich", - "common_private": "privat", - "common_home": "Startseite", - "common_ai": "KI", - "common_dashboards": "Dashboards", - "common_query": "Abfrage", - "common_reports": "Berichte", - "common_alerts": "Warnungen", - "common_status": "Status", - "common_settings": "Einstellungen", - "common_projects": "Projekte", - "common_users": "Benutzer", - "common_error": "Fehler", - "organizations_overview_page_title": "{title} Übersicht - Rill", - "projects_cards_subtitle": "Sehen Sie sich unten Ihre Projekte an.", - "projects_cards_new_project": "+ Neues Projekt", - "projects_cards_empty": "Diese Organisation hat noch keine Projekte.", - "projects_card_visibility_prefix": "Dieses Projekt ist", - "projects_overview_page_title": "{name} - Rill", - "projects_overview_title_aria": "Projekttitel", - "projects_overview_welcome": "Willkommen bei", - "projects_overview_subtitle_chat": "Stellen Sie Fragen zu Ihren Daten oder erkunden Sie unten Ihre Dashboards", - "projects_overview_subtitle": "Erkunden Sie unten Ihre Dashboards", - "projects_layout_error_fetching_deployment": "Fehler beim Abrufen des Deployments", - "projects_layout_deployment_error_header": "Deployment-Fehler", - "projects_layout_deployment_error_body": "Beim Deployen Ihres Projekts ist ein Fehler aufgetreten. Bitte kontaktieren Sie den Support.", - "projects_building_branch": "Branch-Deployment wird gestartet...", - "projects_building_default": "Einen Moment! Wir deployen Ihr Projekt...", - "projects_redeploy_waking": "Ihr Projekt wird aufgeweckt...", - "projects_redeploy_hibernating": "Ihr Projekt befindet sich im Ruhezustand", - "projects_redeploy_wake_button": "Projekt aufwecken", - "projects_redeploy_waking_button": "Wird aufgeweckt...", - "projects_redeploy_hibernating_readonly": "Dieses Projekt befindet sich im Ruhezustand", - "projects_redeploy_contact_admin": "Wenden Sie sich an den Administrator des Projekts, um das Projekt erneut zu deployen.", - "projects_redeploy_wake_failed": "Projekt konnte nicht aufgeweckt werden: {error}", - "dashboards_listing_empty_title": "Sie haben noch keine Dashboards", - "dashboards_listing_empty_create_link": "Erstellen Sie ein Dashboard", - "dashboards_listing_empty_create_suffix": "um loszulegen", - "dashboards_listing_see_all": "Alle Dashboards anzeigen", - "dashboards_listing_last_refreshed": "Zuletzt aktualisiert {time}" -} diff --git a/web-common/src/lib/i18n/messages/en.json b/web-common/src/lib/i18n/messages/en.json index eb78b93665e0..08f23297e2cd 100644 --- a/web-common/src/lib/i18n/messages/en.json +++ b/web-common/src/lib/i18n/messages/en.json @@ -1,49 +1,4 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", - "common_edit": "Edit", - "common_rename": "Rename", - "common_share": "Share", - "common_delete": "Delete", - "common_viewer": "Viewer", - "common_admin": "Admin", - "common_public": "public", - "common_private": "private", - "common_home": "Home", - "common_ai": "AI", - "common_dashboards": "Dashboards", - "common_query": "Query", - "common_reports": "Reports", - "common_alerts": "Alerts", - "common_status": "Status", - "common_settings": "Settings", - "common_projects": "Projects", - "common_users": "Users", - "common_error": "Error", - "organizations_overview_page_title": "{title} overview - Rill", - "projects_cards_subtitle": "Check out your projects below.", - "projects_cards_new_project": "+ New project", - "projects_cards_empty": "This organization has no projects yet.", - "projects_card_visibility_prefix": "This project is", - "projects_overview_page_title": "{name} - Rill", - "projects_overview_title_aria": "Project title", - "projects_overview_welcome": "Welcome to", - "projects_overview_subtitle_chat": "Ask questions about your data, or explore your dashboards below", - "projects_overview_subtitle": "Explore your dashboards below", - "projects_layout_error_fetching_deployment": "Error fetching deployment", - "projects_layout_deployment_error_header": "Deployment Error", - "projects_layout_deployment_error_body": "There was an error deploying your project. Please contact support.", - "projects_building_branch": "Starting branch deployment...", - "projects_building_default": "Hang tight! We're deploying your project...", - "projects_redeploy_waking": "Waking up your project...", - "projects_redeploy_hibernating": "Your project is hibernating", - "projects_redeploy_wake_button": "Wake project", - "projects_redeploy_waking_button": "Waking...", - "projects_redeploy_hibernating_readonly": "This project is hibernating", - "projects_redeploy_contact_admin": "Contact the project's administrator to redeploy the project.", - "projects_redeploy_wake_failed": "Failed to wake project: {error}", - "dashboards_listing_empty_title": "You don't have any dashboards yet", - "dashboards_listing_empty_create_link": "Create a dashboard", - "dashboards_listing_empty_create_suffix": "to get started", - "dashboards_listing_see_all": "See all dashboards", - "dashboards_listing_last_refreshed": "Last refreshed {time}" + "organizations_overview_page_title": "{title} overview - Rill" } diff --git a/web-common/src/lib/i18n/project.inlang/settings.json b/web-common/src/lib/i18n/project.inlang/settings.json index 9bdce4c8cc9b..20ec58d92061 100644 --- a/web-common/src/lib/i18n/project.inlang/settings.json +++ b/web-common/src/lib/i18n/project.inlang/settings.json @@ -1,7 +1,7 @@ { "$schema": "https://inlang.com/schema/project-settings", "baseLocale": "en", - "locales": ["en", "de"], + "locales": ["en"], "modules": [ "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" From dcbf7bd0105e45eecfb364ce7ecf3746ee551a8f Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Mon, 29 Jun 2026 17:11:13 +0530 Subject: [PATCH 6/8] Fix CI --- .github/workflows/web-test-code-quality.yml | 3 +++ .prettierignore | 4 ++++ web-common/src/lib/i18n/README.md | 5 ++--- web-common/src/lib/i18n/project.inlang/.meta.json | 2 +- web-common/src/lib/i18n/project.inlang/README.md | 5 ++++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/web-test-code-quality.yml b/.github/workflows/web-test-code-quality.yml index ae13110a6dc3..6ccc10ccfdf3 100644 --- a/.github/workflows/web-test-code-quality.yml +++ b/.github/workflows/web-test-code-quality.yml @@ -36,6 +36,9 @@ jobs: node-version-file: '.nvmrc' cache: 'npm' + - name: Build i18n files + run: npm run build:i18n + - name: Web code quality checks run: bash ./scripts/web-test-code-quality.sh env: diff --git a/.prettierignore b/.prettierignore index 4e3db204c092..afc52cac9d8e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -66,3 +66,7 @@ web-admin/src/client/gen # generated by nearly web-common/src/features/dashboards/url-state/filters/expression.cjs + +# generated by paraglidejs +web-common/src/lib/i18n/gen +web-common/src/lib/i18n/project.inlang/cache diff --git a/web-common/src/lib/i18n/README.md b/web-common/src/lib/i18n/README.md index 99c2683d7aeb..c4133c47cafa 100644 --- a/web-common/src/lib/i18n/README.md +++ b/web-common/src/lib/i18n/README.md @@ -39,8 +39,7 @@ build, so you rarely need to run `build:i18n` manually during development. import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; - -

{m.welcome_greeting({ name })}

+

{m.welcome_greeting({ name })}

``` - Import the `m` namespace; call messages as functions. @@ -62,7 +61,7 @@ Use `feature_component_purpose`, lower snake_case, grouped by prefix in "common_cancel": "Cancel", "common_save": "Save", "welcome_greeting": "Welcome, {name}", - "dashboards_filters_clear_all": "Clear all filters" + "dashboards_filters_clear_all": "Clear all filters", } ``` diff --git a/web-common/src/lib/i18n/project.inlang/.meta.json b/web-common/src/lib/i18n/project.inlang/.meta.json index baea54877ce3..d39728f7d73e 100644 --- a/web-common/src/lib/i18n/project.inlang/.meta.json +++ b/web-common/src/lib/i18n/project.inlang/.meta.json @@ -1,3 +1,3 @@ { "highestSdkVersion": "2.6.2" -} \ No newline at end of file +} diff --git a/web-common/src/lib/i18n/project.inlang/README.md b/web-common/src/lib/i18n/project.inlang/README.md index e8bd2f80610d..33a7fcbf7016 100644 --- a/web-common/src/lib/i18n/project.inlang/README.md +++ b/web-common/src/lib/i18n/project.inlang/README.md @@ -1,4 +1,3 @@ - ## What is this folder? This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) inlang project. @@ -6,17 +5,21 @@ This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) i ## At a glance Purpose: + - This folder stores inlang project configuration and plugin cache data. - Translation files live outside this folder and are referenced from `settings.json`. Safe to edit: + - `settings.json` Do not edit: + - `cache/` - `.gitignore` Key files: + - `settings.json` — locales, plugins, file patterns (source of truth) - `cache/` — plugin caches (safe to delete) - `.gitignore` — generated From b31e93229aef0a6d02dd4c2dafb0d855b5a0d483 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Mon, 29 Jun 2026 17:19:57 +0530 Subject: [PATCH 7/8] PR comments --- .github/workflows/web-test-code-quality.yml | 3 --- package.json | 2 +- scripts/web-test-code-quality.sh | 18 +++++++++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/web-test-code-quality.yml b/.github/workflows/web-test-code-quality.yml index 6ccc10ccfdf3..ae13110a6dc3 100644 --- a/.github/workflows/web-test-code-quality.yml +++ b/.github/workflows/web-test-code-quality.yml @@ -36,9 +36,6 @@ jobs: node-version-file: '.nvmrc' cache: 'npm' - - name: Build i18n files - run: npm run build:i18n - - name: Web code quality checks run: bash ./scripts/web-test-code-quality.sh env: diff --git a/package.json b/package.json index 1c21c92dbc3e..a3e4b51adfe3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build:i18n": "paraglide-js compile --project web-common/src/lib/i18n/project.inlang --outdir web-common/src/lib/i18n/gen", - "build": "npm run build:i18n & npm run build -w web-local", + "build": "npm run build:i18n && npm run build -w web-local", "dev": "sh -c 'npm run dev-runtime -- \"$@\" & npm run dev-web -- --port 3001 & wait' --", "dev-web": "npm run dev -w web-local -- ", "dev-runtime": "node scripts/dev.js", diff --git a/scripts/web-test-code-quality.sh b/scripts/web-test-code-quality.sh index 7fde01a8fe83..898b3343d196 100755 --- a/scripts/web-test-code-quality.sh +++ b/scripts/web-test-code-quality.sh @@ -71,6 +71,10 @@ echo "" echo "== NPM Install ==" npm ci +echo "" +echo "== Build i18n files ==" +npm run build:i18n + if [[ "$COMMON" == "true" ]]; then echo "" echo "== lint and type checks for web common ==" @@ -79,13 +83,17 @@ if [[ "$COMMON" == "true" ]]; then cd .. npx eslint web-common --quiet || exit_code=$? npx svelte-check --workspace web-common --no-tsconfig || exit_code=$? - echo "" - echo "== i18n guard for migrated areas (warning only) ==" - # Reports hardcoded strings in already-migrated areas. Non-fatal for now; - # the final i18n migration chunk adds --strict to make it fatal. - node ./scripts/i18n-guard.js || true fi +echo "" +echo "== i18n guard for migrated areas (warning only) ==" +# Scans a fixed set of already-migrated areas on the filesystem, so it runs +# unconditionally rather than under an app filter: the migrated areas span +# multiple apps and are independent of which files a given PR touched. +# Reports hardcoded strings; non-fatal for now: the final i18n migration chunk +# adds --strict to make it fatal. +node ./scripts/i18n-guard.js || true + if [[ "$LOCAL" == "true" ]]; then echo "" echo "== lint and type checks for web local ==" From d75100db0e1eea48a22d6df253dbab19ba0c48aa Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Tue, 30 Jun 2026 10:49:14 +0530 Subject: [PATCH 8/8] PR comments --- .gitignore | 3 +++ web-common/src/lib/i18n/README.md | 26 +++++++++---------- .../src/lib/i18n/project.inlang/.gitignore | 19 -------------- web-local/src/hooks.ts | 4 +-- 4 files changed, 17 insertions(+), 35 deletions(-) delete mode 100644 web-common/src/lib/i18n/project.inlang/.gitignore diff --git a/.gitignore b/.gitignore index d7ebf511784b..b2b96db7a830 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ vite.config.ts.timestamp-* # TypeScript incremental build cache *.tsbuildinfo + +# inlang manages this file itself (^2.5); keep it untracked to avoid churn +**/*.inlang/.gitignore diff --git a/web-common/src/lib/i18n/README.md b/web-common/src/lib/i18n/README.md index c4133c47cafa..10c89d33fafb 100644 --- a/web-common/src/lib/i18n/README.md +++ b/web-common/src/lib/i18n/README.md @@ -5,20 +5,18 @@ Rill's frontend is localized with [Paraglide JS](https://inlang.com/m/gerre34r/l tree-shakeable, type-safe message functions consumed by both `web-local` (Rill Developer) and `web-admin` (Rill Cloud). -See [`MIGRATION.md`](./MIGRATION.md) for the chunked plan to migrate existing -hardcoded strings. - ## Layout ``` lib/i18n/ -├── messages/{en,de}.json # source translations (edit these) -├── project.inlang/ # inlang project config -└── gen/ # compiled message functions (auto-generated, gitignored) +├── messages/en.json # source translations (edit these) +├── project.inlang/ # inlang project config +└── gen/ # compiled message functions (auto-generated, gitignored) ``` - `messages/en.json` is the base locale and the source of truth for keys. -- `messages/de.json` is generated via machine translation; do not hand-maintain it. + English is currently the only configured locale; add more by listing them in + `project.inlang/settings.json` and adding a `messages/{locale}.json` file. - `gen/` is compiled output. It is gitignored — never edit or import individual files by hand other than the documented entry points below. @@ -26,7 +24,6 @@ lib/i18n/ ```sh npm run build:i18n # compile messages/ -> gen/ -npm run machine-translate # fill in non-English locales from en.json ``` The Vite plugin in both `web-local` and `web-admin` recompiles `gen/` on dev and @@ -46,8 +43,10 @@ build, so you rarely need to run `build:i18n` manually during development. - Pass interpolation values as a named object: `m.welcome_greeting({ name })`. - Override the locale per call when needed: `m.common_cancel({}, { locale: "de" })`. -Locale detection uses the `["preferredLanguage", "baseLocale"]` strategy: the -browser's preferred language, falling back to English. +Locale detection is configured per app in each `vite.config.ts`. `web-local` +uses `["preferredLanguage", "baseLocale"]` (the browser's preferred language, +falling back to English); `web-admin` adds a `localStorage` step in front so a +user's explicit choice persists. ## Conventions @@ -85,10 +84,9 @@ rather than hand-rolled `count === 1 ? ... : ...` logic. ## Adding or migrating a string 1. Add the key to `messages/en.json` following the naming convention. -2. Run `npm run machine-translate` to populate other locales. -3. Replace the literal in code with `m.key()` (or `m.key({ var })`). -4. `npm run build:i18n` (or rely on the Vite plugin in dev). -5. Run `npm run test -w web-common` and `npm run quality`. +2. Replace the literal in code with `m.key()` (or `m.key({ var })`). +3. `npm run build:i18n` (or rely on the Vite plugin in dev). +4. Run `npm run test -w web-common` and `npm run quality`. ## Guard against new hardcoded strings diff --git a/web-common/src/lib/i18n/project.inlang/.gitignore b/web-common/src/lib/i18n/project.inlang/.gitignore deleted file mode 100644 index 433b48c811f1..000000000000 --- a/web-common/src/lib/i18n/project.inlang/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# IF GIT SHOWED THAT THIS FILE CHANGED -# -# 1. RUN THE FOLLOWING COMMAND -# -# --- -# git rm --cached '**/*.inlang/.gitignore' -# --- -# -# 2. COMMIT THE CHANGE -# -# --- -# git commit -m "fix: remove tracked .gitignore from inlang project" -# --- -# -# Inlang handles the gitignore itself starting with version ^2.5. -# -# everything is ignored except settings.json -cache -!settings.json \ No newline at end of file diff --git a/web-local/src/hooks.ts b/web-local/src/hooks.ts index 32692100fb4a..7bbe52dc202f 100644 --- a/web-local/src/hooks.ts +++ b/web-local/src/hooks.ts @@ -1,6 +1,6 @@ import type { Reroute } from "@sveltejs/kit"; import { deLocalizeUrl } from "@rilldata/web-common/lib/i18n/gen/runtime"; -export const reroute: Reroute = (request) => { - return deLocalizeUrl(request.url).pathname; +export const reroute: Reroute = ({ url }) => { + return deLocalizeUrl(url).pathname; };