mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-21 00:28:08 +08:00
Merge pull request #2776 from element-hq/quenting/locales-as-assets
Handle locales as Vite assets
This commit is contained in:
commit
4c1c818ba9
4
.github/workflows/translations-download.yaml
vendored
4
.github/workflows/translations-download.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
run: "yarn install --frozen-lockfile"
|
run: "yarn install --frozen-lockfile"
|
||||||
|
|
||||||
- name: Prune i18n
|
- name: Prune i18n
|
||||||
run: "rm -R public/locales"
|
run: "rm -R locales"
|
||||||
|
|
||||||
- name: Download translation files
|
- name: Download translation files
|
||||||
uses: localazy/download@0a79880fb66150601e3b43606fab69c88123c087 # v1.1.0
|
uses: localazy/download@0a79880fb66150601e3b43606fab69c88123c087 # v1.1.0
|
||||||
@ -32,7 +32,7 @@ jobs:
|
|||||||
groups: "-p includeSourceLang:true"
|
groups: "-p includeSourceLang:true"
|
||||||
|
|
||||||
- name: Fix the owner of the downloaded files
|
- 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
|
- name: Prettier
|
||||||
run: yarn prettier:format
|
run: yarn prettier:format
|
||||||
|
@ -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. 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
|
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
|
```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:
|
the English translation:
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
|
@ -22,7 +22,7 @@ export default {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
locales: ["en-GB"],
|
locales: ["en-GB"],
|
||||||
output: "public/locales/$LOCALE/$NAMESPACE.json",
|
output: "locales/$LOCALE/$NAMESPACE.json",
|
||||||
input: ["src/**/*.{ts,tsx}"],
|
input: ["src/**/*.{ts,tsx}"],
|
||||||
sort: true,
|
sort: true,
|
||||||
};
|
};
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
"features": ["plural_postfix_us", "filter_untranslated"],
|
"features": ["plural_postfix_us", "filter_untranslated"],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"pattern": "public/locales/en-GB/*.json",
|
"pattern": "locales/en-GB/*.json",
|
||||||
"lang": "inherited"
|
"lang": "inherited"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "existing",
|
"group": "existing",
|
||||||
"pattern": "public/locales/*/*.json",
|
"pattern": "locales/*/*.json",
|
||||||
"excludes": ["public/locales/en-GB/*.json"],
|
"excludes": ["locales/en-GB/*.json"],
|
||||||
"lang": "${autodetectLang}"
|
"lang": "${autodetectLang}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"download": {
|
"download": {
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"output": "public/locales/${langLsrDash}/${file}"
|
"output": "locales/${langLsrDash}/${file}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"includeSourceLang": "${includeSourceLang|false}",
|
"includeSourceLang": "${includeSourceLang|false}",
|
||||||
|
@ -84,7 +84,6 @@
|
|||||||
"history": "^4.0.0",
|
"history": "^4.0.0",
|
||||||
"i18next": "^23.0.0",
|
"i18next": "^23.0.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.0.0",
|
|
||||||
"i18next-parser": "^9.0.0",
|
"i18next-parser": "^9.0.0",
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"knip": "^5.27.2",
|
"knip": "^5.27.2",
|
||||||
|
2
src/@types/i18next.d.ts
vendored
2
src/@types/i18next.d.ts
vendored
@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import "i18next";
|
import "i18next";
|
||||||
// import all namespaces (for the default language, only)
|
// 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" {
|
declare module "i18next" {
|
||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
|
@ -5,10 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
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 { initReactI18next } from "react-i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import Backend from "i18next-http-backend";
|
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
|
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 { ElementCallOpenTelemetry } from "./otel/otel";
|
||||||
import { platform } from "./Platform";
|
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 {
|
enum LoadState {
|
||||||
None,
|
None,
|
||||||
Loading,
|
Loading,
|
||||||
@ -74,6 +139,7 @@ export class Initializer {
|
|||||||
nsSeparator: false,
|
nsSeparator: false,
|
||||||
pluralSeparator: "_",
|
pluralSeparator: "_",
|
||||||
contextSeparator: "|",
|
contextSeparator: "|",
|
||||||
|
supportedLngs,
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // React has built-in XSS protections
|
escapeValue: false, // React has built-in XSS protections
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@ import { cleanup } from "@testing-library/react";
|
|||||||
import "vitest-axe/extend-expect";
|
import "vitest-axe/extend-expect";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
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";
|
import { Config } from "./config/Config";
|
||||||
|
|
||||||
// Bare-minimum i18n config
|
// Bare-minimum i18n config
|
||||||
|
@ -60,6 +60,25 @@ export default defineConfig(({ mode }) => {
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true,
|
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,
|
plugins,
|
||||||
resolve: {
|
resolve: {
|
||||||
|
16
yarn.lock
16
yarn.lock
@ -4138,13 +4138,6 @@ cosmiconfig@^8.1.3:
|
|||||||
parse-json "^5.2.0"
|
parse-json "^5.2.0"
|
||||||
path-type "^4.0.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:
|
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
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:
|
dependencies:
|
||||||
"@babel/runtime" "^7.23.2"
|
"@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:
|
i18next-parser@^9.0.0:
|
||||||
version "9.0.2"
|
version "9.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-9.0.2.tgz#f9d627422d33c352967556c8724975d58f1f5a95"
|
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"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
|
||||||
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
|
||||||
|
|
||||||
node-fetch@^2.6.12, node-fetch@^2.6.7:
|
node-fetch@^2.6.7:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||||
|
Loading…
Reference in New Issue
Block a user