Skip to content

Commit d6a715c

Browse files
committed
feat(i18n): add server-side i18n for notification agents
Adds @formatjs/intl-based translation support to all notification agents, allowing notifications to be sent in each user's preferred language or a configurable system locale for channel-based agents. fix #1576, fix #1706
1 parent 25e376c commit d6a715c

27 files changed

Lines changed: 1001 additions & 293 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
7474
This usually happens when you've added or modified translation strings in your code but haven't updated the translation file.
7575
76-
Please run `pnpm i18n:extract` and commit the changes.
76+
Please run `pnpm i18n:extract` and/or `pnpm i18n:extract:server` and commit the changes.
7777
run: |
7878
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
7979
check_failed=0; node bin/check-i18n.js || check_failed=$?

bin/check-i18n.js

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,52 @@
11
#!/usr/bin/env node
2-
2+
/* eslint-disable @typescript-eslint/no-require-imports, no-console */
33
/**
44
* Check that i18n locale files are in sync with extracted messages.
5-
* Runs `pnpm i18n:extract` and compares en.json; exits 1 if they differ.
5+
* Runs extract scripts and compares en.json; exits 1 if they differ.
66
*/
77
const { execSync } = require('child_process');
88
const fs = require('fs');
99
const path = require('path');
1010

11-
const localePath = path.join(
12-
__dirname,
13-
'..',
14-
'src',
15-
'i18n',
16-
'locale',
17-
'en.json'
18-
);
19-
const backupPath = `${localePath}.bak`;
11+
const targets = [
12+
{
13+
localePath: path.join(__dirname, '..', 'src', 'i18n', 'locale', 'en.json'),
14+
script: 'pnpm i18n:extract',
15+
},
16+
{
17+
localePath: path.join(
18+
__dirname,
19+
'..',
20+
'server',
21+
'lib',
22+
'i18n',
23+
'locale',
24+
'en.json'
25+
),
26+
script: 'pnpm i18n:extract:server',
27+
},
28+
];
2029

21-
try {
22-
fs.copyFileSync(localePath, backupPath);
23-
execSync('pnpm i18n:extract', { stdio: 'inherit' });
24-
const original = fs.readFileSync(backupPath, 'utf8');
25-
const extracted = fs.readFileSync(localePath, 'utf8');
26-
fs.unlinkSync(backupPath);
30+
for (const { localePath, script } of targets) {
31+
const backupPath = `${localePath}.bak`;
32+
try {
33+
fs.copyFileSync(localePath, backupPath);
34+
execSync(script, { stdio: 'inherit' });
2735

28-
if (original !== extracted) {
29-
console.error(
30-
"i18n messages are out of sync. Please run 'pnpm i18n:extract' and commit the changes."
31-
);
32-
process.exit(1);
33-
}
34-
} catch (err) {
35-
if (fs.existsSync(backupPath)) {
36+
const original = fs.readFileSync(backupPath, 'utf8');
37+
const extracted = fs.readFileSync(localePath, 'utf8');
3638
fs.unlinkSync(backupPath);
39+
40+
if (original !== extracted) {
41+
console.error(
42+
`i18n messages are out of sync. Please run '${script}' and commit the changes.`
43+
);
44+
process.exit(1);
45+
}
46+
} catch (err) {
47+
if (fs.existsSync(backupPath)) {
48+
fs.unlinkSync(backupPath);
49+
}
50+
throw err;
3751
}
38-
throw err;
3952
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
"preinstall": "npx only-allow pnpm",
88
"postinstall": "next telemetry disable",
99
"dev": "nodemon -e ts --watch server --watch seerr-api.yml -e .json,.ts,.yml -x ts-node -r tsconfig-paths/register --files --project server/tsconfig.json server/index.ts",
10-
"build:server": "tsc --project server/tsconfig.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && tsc-alias -p server/tsconfig.json",
10+
"build:server": "tsc --project server/tsconfig.json && copyfiles -u 2 server/templates/**/*.{html,pug} dist/templates && copyfiles -u 2 \"server/lib/i18n/locale/*.json\" dist/lib/i18n/locale && tsc-alias -p server/tsconfig.json",
1111
"build:next": "next build",
1212
"build": "pnpm build:next && pnpm build:server",
1313
"lint": "eslint \"./server/**/*.{ts,tsx}\" \"./src/**/*.{ts,tsx}\" --cache",
1414
"lintfix": "eslint \"./server/**/*.{ts,tsx}\" \"./src/**/*.{ts,tsx}\" --fix",
1515
"test": "node server/test/index.mts",
1616
"start": "NODE_ENV=production node dist/index.js",
1717
"i18n:extract": "ts-node --project server/tsconfig.json src/i18n/extractMessages.ts",
18+
"i18n:extract:server": "ts-node --project server/tsconfig.json server/lib/i18n/extractMessages.ts",
1819
"migration:generate": "ts-node -r tsconfig-paths/register --project server/tsconfig.json ./node_modules/typeorm/cli.js migration:generate -d server/datasource.ts",
1920
"migration:create": "ts-node -r tsconfig-paths/register --project server/tsconfig.json ./node_modules/typeorm/cli.js migration:create -d server/datasource.ts",
2021
"migration:run": "ts-node -r tsconfig-paths/register --project server/tsconfig.json ./node_modules/typeorm/cli.js migration:run -d server/datasource.ts",
@@ -36,6 +37,7 @@
3637
"dependencies": {
3738
"@dr.pogodin/csurf": "^1.16.6",
3839
"@fontsource-variable/inter": "^5.2.8",
40+
"@formatjs/intl": "^4.1.4",
3941
"@formatjs/intl-displaynames": "6.8.13",
4042
"@formatjs/intl-locale": "3.1.1",
4143
"@formatjs/intl-pluralrules": "5.4.6",

pnpm-lock.yaml

Lines changed: 72 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import DiscoverSlider from '@server/entity/DiscoverSlider';
55
import { Session } from '@server/entity/Session';
66
import { User } from '@server/entity/User';
77
import { startJobs } from '@server/job/schedule';
8+
import { initI18n } from '@server/lib/i18n';
89
import notificationManager from '@server/lib/notifications';
910
import DiscordAgent from '@server/lib/notifications/agents/discord';
1011
import EmailAgent from '@server/lib/notifications/agents/email';
@@ -82,6 +83,8 @@ app
8283
const settings = await getSettings().load();
8384
restartFlag.initializeSettings(settings);
8485

86+
initI18n();
87+
8588
if (settings.network.forceIpv4First) {
8689
axios.defaults.httpAgent = new http.Agent({ family: 4 });
8790
axios.defaults.httpsAgent = new https.Agent({ family: 4 });

0 commit comments

Comments
 (0)