Merge pull request #2776 from element-hq/quenting/locales-as-assets

Handle locales as Vite assets
This commit is contained in:
Quentin Gliech authored and GitHub committed 2024-11-15 08:41:56 +01:00
commit 4c1c818ba9
32 files changed
+99 -29

No files matched your search

+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
run: "yarn install --frozen-lockfile"
- name: Prune i18n
run: "rm -R public/locales"
run: "rm -R locales"
- name: Download translation files
uses: localazy/download@0a79880fb66150601e3b43606fab69c88123c087 # v1.1.0
@@ -32,7 +32,7 @@ jobs:
groups: "-p includeSourceLang:true"
- name: Fix the owner of the downloaded files
run: "sudo chown runner:docker -R public/locales"
run: "sudo chown runner:docker -R locales"
- name: Prettier
run: yarn prettier:format
+2 -2
View File
@@ -213,7 +213,7 @@ To add a new translation key you can do these steps:
1. Add the new key entry to the code where the new key is used: `t("some_new_key")`
1. Run `yarn i18n` to extract the new key and update the translation files. This
will add a skeleton entry to the `public/locales/en-GB/app.json` file:
will add a skeleton entry to the `locales/en-GB/app.json` file:
```jsonc
{
...
@@ -221,7 +221,7 @@ To add a new translation key you can do these steps:
...
}
```
1. Update the skeleton entry in the `public/locales/en-GB/app.json` file with
1. Update the skeleton entry in the `locales/en-GB/app.json` file with
the English translation:
```jsonc
+1 -1
View File
@@ -22,7 +22,7 @@ export default {
],
},
locales: ["en-GB"],
output: "public/locales/$LOCALE/$NAMESPACE.json",
output: "locales/$LOCALE/$NAMESPACE.json",
input: ["src/**/*.{ts,tsx}"],
sort: true,
};
+4 -4
View File
@@ -7,13 +7,13 @@
"features": ["plural_postfix_us", "filter_untranslated"],
"files": [
{
"pattern": "public/locales/en-GB/*.json",
"pattern": "locales/en-GB/*.json",
"lang": "inherited"
},
{
"group": "existing",
"pattern": "public/locales/*/*.json",
"excludes": ["public/locales/en-GB/*.json"],
"pattern": "locales/*/*.json",
"excludes": ["locales/en-GB/*.json"],
"lang": "${autodetectLang}"
}
]
@@ -22,7 +22,7 @@
"download": {
"files": [
{
"output": "public/locales/${langLsrDash}/${file}"
"output": "locales/${langLsrDash}/${file}"
}
],
"includeSourceLang": "${includeSourceLang|false}",
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-1
View File
@@ -84,7 +84,6 @@
"history": "^4.0.0",
"i18next": "^23.0.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.0.0",
"i18next-parser": "^9.0.0",
"jsdom": "^25.0.0",
"knip": "^5.27.2",
+1 -1
View File
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
import "i18next";
// import all namespaces (for the default language, only)
import app from "../../public/locales/en-GB/app.json";
import app from "../../locales/en-GB/app.json";
declare module "i18next" {
interface CustomTypeOptions {
+68 -2
View File
@@ -5,10 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import i18n from "i18next";
import i18n, {
type BackendModule,
type ReadCallback,
type ResourceKey,
} from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import * as Sentry from "@sentry/react";
import { logger } from "matrix-js-sdk/src/logger";
import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
@@ -19,6 +22,68 @@ import { Config } from "./config/Config";
import { ElementCallOpenTelemetry } from "./otel/otel";
import { platform } from "./Platform";
// This generates a map of locale names to their URL (based on import.meta.url), which looks like this:
// {
// "../locales/en-GB/app.json": "/whatever/assets/root/locales/en-aabbcc.json",
// ...
// }
const locales = import.meta.glob<string>("../locales/*/*.json", {
query: "?url",
import: "default",
eager: true,
});
const getLocaleUrl = (
language: string,
namespace: string,
): string | undefined => locales[`../locales/${language}/${namespace}.json`];
const supportedLngs = [
...new Set(
Object.keys(locales).map((url) => {
// The URLs are of the form ../locales/en-GB/app.json
// This extracts the language code from the URL
const lang = url.match(/\/([^/]+)\/[^/]+\.json$/)?.[1];
if (!lang) {
throw new Error(`Could not parse locale URL ${url}`);
}
return lang;
}),
),
];
// A backend that fetches the locale files from the URLs generated by the glob above
const Backend = {
type: "backend",
init(): void {},
read(language: string, namespace: string, callback: ReadCallback): void {
(async (): Promise<ResourceKey> => {
const url = getLocaleUrl(language, namespace);
if (!url) {
throw new Error(
`Namespace ${namespace} for locale ${language} not found`,
);
}
const response = await fetch(url, {
credentials: "omit",
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw Error(`Failed to fetch ${url}`);
}
return await response.json();
})().then(
(data) => callback(null, data),
(error) => callback(error, null),
);
},
} satisfies BackendModule;
enum LoadState {
None,
Loading,
@@ -74,6 +139,7 @@ export class Initializer {
nsSeparator: false,
pluralSeparator: "_",
contextSeparator: "|",
supportedLngs,
interpolation: {
escapeValue: false, // React has built-in XSS protections
},
+1 -1
View File
@@ -16,7 +16,7 @@ import { cleanup } from "@testing-library/react";
import "vitest-axe/extend-expect";
import { logger } from "matrix-js-sdk/src/logger";
import EN_GB from "../public/locales/en-GB/app.json";
import EN_GB from "../locales/en-GB/app.json";
import { Config } from "./config/Config";
// Bare-minimum i18n config
+19
View File
@@ -60,6 +60,25 @@ export default defineConfig(({ mode }) => {
},
build: {
sourcemap: true,
rollupOptions: {
output: {
assetFileNames: ({ originalFileNames }) => {
if (originalFileNames) {
for (const name of originalFileNames) {
// Custom asset name for locales to include the locale code in the filename
const match = name.match(/locales\/([^/]+)\/(.+)\.json$/);
if (match) {
const [, locale, filename] = match;
return `assets/${locale}-${filename}-[hash].json`;
}
}
}
// Default naming fallback
return "assets/[name]-[hash][extname]";
},
},
},
},
plugins,
resolve: {
+1 -15
View File
@@ -4138,13 +4138,6 @@ cosmiconfig@^8.1.3:
parse-json "^5.2.0"
path-type "^4.0.0"
cross-fetch@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
dependencies:
node-fetch "^2.6.12"
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -5469,13 +5462,6 @@ i18next-browser-languagedetector@^8.0.0:
dependencies:
"@babel/runtime" "^7.23.2"
i18next-http-backend@^2.0.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.6.2.tgz#b25516446ae6f251ce8231e70e6ffbca833d46a5"
integrity sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==
dependencies:
cross-fetch "4.0.0"
i18next-parser@^9.0.0:
version "9.0.2"
resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-9.0.2.tgz#f9d627422d33c352967556c8724975d58f1f5a95"
@@ -6331,7 +6317,7 @@ node-addon-api@^7.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch@^2.6.12, node-fetch@^2.6.7:
node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==