Skip to content

Commit

Permalink
feat: integrate i18n infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
ehsanmmd committed Mar 19, 2024
1 parent 4baa04f commit bf1460b
Show file tree
Hide file tree
Showing 24 changed files with 1,072 additions and 300 deletions.
7 changes: 6 additions & 1 deletion .eslintrc-typescript.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const unusedIgnorePattern = "^_";

module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended",
// prettier needs to come last
"prettier",
],
Expand Down Expand Up @@ -148,7 +151,9 @@ module.exports = {
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
argsIgnorePattern: unusedIgnorePattern,
varsIgnorePattern: unusedIgnorePattern,
destructuredArrayIgnorePattern: unusedIgnorePattern,
},
],
"@typescript-eslint/no-useless-constructor": "warn",
Expand Down
28 changes: 27 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
/* eslint-env node */
const unusedIgnorePattern = "^_";

module.exports = {
extends: ["eslint:recommended", "plugin:storybook/recommended", "prettier"],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: [
"react",
"react-hooks",
"@typescript-eslint",
"@tanstack/query",
"formatjs",
],
rules: {
curly: ["error", "all"],
"no-console": [
Expand All @@ -14,9 +22,27 @@ module.exports = {
allow: ["warn", "error"],
},
],
"no-unused-vars": "error",
"no-unused-vars": [
"error",
{
argsIgnorePattern: unusedIgnorePattern,
varsIgnorePattern: unusedIgnorePattern,
},
],
"no-extra-boolean-cast": "off",
eqeqeq: "error",
"formatjs/no-extra-boolean-cast": "off",
"formatjs/enforce-default-message": "error",
"formatjs/enforce-placeholders": "error",
"formatjs/no-multiple-whitespaces": "error",
"formatjs/no-multiple-plurals": "error",
"formatjs/no-invalid-icu": "error",
"formatjs/no-id": "error",
"formatjs/no-offset": "error",
"formatjs/no-complex-selectors": "error",
"formatjs/no-useless-message": "error",
"formatjs/prefer-formatted-message": "error",
"formatjs/prefer-pound-in-plural": "error",
},
overrides: [
{
Expand Down
11 changes: 10 additions & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"plugins": ["babel-plugin-styled-components"],
"plugins": [
"babel-plugin-styled-components",
[
"formatjs",
{
// keep in sync with lang/extract-messages.ts
"idInterpolationPattern": "[sha512:contenthash:base64:16]" // keep in sync with the hash used in extract script in package.json
}
]
],
"presets": [
[
"@babel/preset-env",
Expand Down
70 changes: 70 additions & 0 deletions lang/extract-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readdir, writeFile } from "node:fs/promises";
import { extract, compile } from "@formatjs/cli-lib";
import { resolve } from "path";
import process from "process";
import url from "url";
import type { MessageDescriptor } from "@formatjs/cli-lib";
import { supportedLocales } from "./supportedLocales.ts";

const dirname = url.fileURLToPath(new URL(".", import.meta.url));
const localesFolder = resolve(dirname, "locales");
const messagesPath = resolve(dirname, "messages.json");

async function main() {
try {
await extractMessages();
await compileLocales();
} catch (err: unknown) {
console.error(err); // eslint-disable-line no-console
}
}

async function extractMessages() {
const patternsToExclude = ["/src/api/generated/", "/src/stories/", ".d.ts"];

const files = (
await readdir(resolve(process.cwd(), "src/"), {
recursive: true,
withFileTypes: true,
})
)
.filter(
entry =>
(entry.isFile() && entry.name.endsWith("ts")) ||
entry.name.endsWith("tsx"),
)
.map(entry => resolve(entry.path, entry.name))
.filter(filePath => !patternsToExclude.some(dir => filePath.includes(dir)));

const resultAsString = await extract(files, {
idInterpolationPattern: "[sha512:contenthash:base64:16]",
flatten: true,
extractSourceLocation: true,
});

// we want to omit the attributes start and end as they only describe the
// tokens in the token stream which is most likely irrelevant to the translator
const result = Object.fromEntries(
Object.entries(
JSON.parse(resultAsString) as Record<string, MessageDescriptor>,
).map<[string, MessageDescriptor]>(
([k, { start: _ignoreStart, end: _ignoreEnd, ...rest }]) => [k, rest],
),
);

await writeFile(messagesPath, JSON.stringify(result, undefined, 4), {
encoding: "utf8",
});
}

async function compileLocales() {
const localeAsString = await compile([messagesPath]);

for (const locale of supportedLocales) {
await writeFile(resolve(localesFolder, `${locale}.json`), localeAsString, {
encoding: "utf8",
});
}
}

void main();
3 changes: 3 additions & 0 deletions lang/locales/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "Title"
}
3 changes: 3 additions & 0 deletions lang/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "Title"
}
3 changes: 3 additions & 0 deletions lang/locales/fa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "Title"
}
9 changes: 9 additions & 0 deletions lang/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"NNQFHO0+LVD2AC/L": {
"col": 8,
"defaultMessage": "Title",
"description": "A title for the test page",
"file": "/home/ehsan/repos/sparse.tech/react-ts-webpack-template/src/pages/test.tsx",
"line": 29
}
}
1 change: 1 addition & 0 deletions lang/supportedLocales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const supportedLocales = ["en", "de", "fa"] as const;
3 changes: 3 additions & 0 deletions lang/translations/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "Diese Seite wurde zu Testzwecken erstellt"
}
3 changes: 3 additions & 0 deletions lang/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "This page has been created for testing purposes"
}
3 changes: 3 additions & 0 deletions lang/translations/fa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NNQFHO0+LVD2AC/L": "این صفحه برای اهداف آزمایشی ساخته شده است"
}
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"prepare": "husky install",
"test:unit": "jest test/unit",
"storybook": "storybook dev -p 6006",
"i18n:extract": "node --loader ts-node/esm ./lang/extract-messages.ts",
"build-storybook": "storybook build"

},
"keywords": [],
"author": "",
Expand All @@ -31,6 +31,8 @@
"@babel/preset-typescript": "^7.23.3",
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@formatjs/cli": "^6.2.7",
"@formatjs/cli-lib": "^6.3.6",
"@jest/globals": "^29.7.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@storybook/addon-essentials": "^7.6.17",
Expand All @@ -41,16 +43,20 @@
"@storybook/react-webpack5": "^7.6.17",
"@storybook/testing-library": "^0.2.2",
"@svgr/webpack": "^8.1.0",
"@tanstack/eslint-plugin-query": "^5.27.7",
"@types/node": "^20.11.28",
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"babel-loader": "^9.1.3",
"babel-plugin-formatjs": "^10.5.13",
"babel-plugin-styled-components": "^2.1.4",
"css-loader": "^6.10.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-formatjs": "^4.12.2",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-storybook": "^0.8.0",
Expand All @@ -61,16 +67,20 @@
"react-refresh": "^0.14.0",
"storybook": "^7.6.17",
"style-loader": "^3.3.4",
"ts-node": "^10.9.2",
"type-fest": "^4.11.1",
"typescript": "^5.4.2",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.2"
},
"dependencies": {
"@tanstack/react-query": "^5.28.0",
"@tanstack/react-query-devtools": "^5.28.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-intl": "^6.6.2",
"styled-components": "^6.1.8"
}
}
Loading

0 comments on commit bf1460b

Please sign in to comment.