refactor: update Next.js, React.js and Node.js (#815)
* refactor: update Next.js and React.js * refactor: update Next.js images * refactor: update ESLint rules and fix warnings/errors * fix: remove old intl polyfill * fix: add proper size to next/image components * fix: adjust full-size for next/image components * fix: temporary allow all domains for image optimization * build: fixes an issue where dev env could lead to javascript heap out of memory * fix: resolve webpack cache issue with country-flag-icons * refactor: switch compiler from Babel to SWC * fix: resize logo in sidebar * fix: break word on long path to avoid text overflow * chore: added sharp for production image optimisation * fix: change extract script for i18n to a custom script * fix: resolve GitHub CodeQL alert * chore: temporarily remove builds for ARMv7 * fix: resize avatar images * refactor: update Node.js to v20 * fix: resolve various UI issues * build: migrate yarn to pnpm and restrict engine to node@^20.0.0 * ci: specify the pnpm version to use in workflow actions * ci: fix typo in pnpm action-setup for cypress workflow * test(cypress): use pnpm instead of yarn * style: ran prettier on pnpm-lock * ci(cypress): setup nodejs v20 in cypress workflow * ci: pnpm cache to reduce install time * ci: use sh shell to get pnpm store directory * build(dockerfile): migrate to pnpm from yarn in docker builds * build(dockerfile): copy the proper pnpm lockfile * build: install pnpm for all platforms * build(dockerfile): remove unnecessary `&&` on apk installation steps * build: migrate pnpm 8 to 9 * build(dockerfile): add node-gyp back in * build(dockerfile): install node-gyp through npm * build(dockerfile): ignore scripts to not run husky install when devdependencies are pruned * build: migrate to pnpm from yarn * chore: remove a section that is no longer relevant --------- Co-authored-by: fallenbagel <98979876+Fallenbagel@users.noreply.github.com>
This commit is contained in:
113
src/i18n/extractMessages.ts
Normal file
113
src/i18n/extractMessages.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
// get all file content recursively
|
||||
async function getFiles(dir: string): Promise<string[]> {
|
||||
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(
|
||||
dirents.map((dirent) => {
|
||||
const res = join(dir, dirent.name);
|
||||
return dirent.isDirectory() ? getFiles(res) : res;
|
||||
})
|
||||
);
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
|
||||
// extract the i18n messages from the file
|
||||
async function extractMessages(
|
||||
filePath: string
|
||||
): Promise<{ namespace: string; messages: Record<string, string> } | null> {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const regex = /defineMessages\(\n?\s*'(.+?)',\n?\s*\{([\s\S]+?)\}\n?\);/;
|
||||
const match = content.match(regex);
|
||||
if (match) {
|
||||
const [, namespace, messages] = match;
|
||||
try {
|
||||
const formattedMessages = messages
|
||||
.trim()
|
||||
.replace(/^\s*(['"])?([a-zA-Z0-9_-]+)(['"])?:/gm, '"$2":')
|
||||
.replace(
|
||||
/'.*'/g,
|
||||
(match) =>
|
||||
`"${match
|
||||
.match(/'(.*)'/)?.[1]
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')}"`
|
||||
)
|
||||
.replace(/,$/, '');
|
||||
const messagesJson = JSON.parse(`{${formattedMessages}}`);
|
||||
return { namespace: namespace.trim(), messages: messagesJson };
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function processMessages(dir: string): Promise<string> {
|
||||
// get messages from all files and sort them by namespace
|
||||
const files = await getFiles(dir);
|
||||
const extractedMessagesGroups = await Promise.all(files.map(extractMessages));
|
||||
|
||||
// group messages by namespace
|
||||
const messagesByNamespace: {
|
||||
namespace: string;
|
||||
messages: Record<string, string>;
|
||||
}[] = [];
|
||||
const namespaces = [
|
||||
...new Set(extractedMessagesGroups.map((msg) => msg?.namespace)),
|
||||
];
|
||||
for (const namespace of namespaces) {
|
||||
if (!namespace) continue;
|
||||
const filteredMessagesGroups = extractedMessagesGroups
|
||||
.filter((msg) => msg?.namespace === namespace)
|
||||
.map((msg) => msg?.messages);
|
||||
for (const extractedMessages of filteredMessagesGroups) {
|
||||
if (!extractedMessages) continue;
|
||||
const previousNamespaceMessages = messagesByNamespace.find(
|
||||
(msg) => msg.namespace === namespace
|
||||
);
|
||||
if (previousNamespaceMessages) {
|
||||
Object.assign(previousNamespaceMessages.messages, extractedMessages);
|
||||
} else {
|
||||
messagesByNamespace.push({ namespace, messages: extractedMessages });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messagesByNamespace.sort((a, b) => {
|
||||
if (!a || !b) return 0;
|
||||
if (
|
||||
a.namespace.startsWith(b.namespace) ||
|
||||
b.namespace.startsWith(a.namespace)
|
||||
) {
|
||||
const aLevel = a.namespace.match(/\./g)?.length || 0;
|
||||
const bLevel = b.namespace.match(/\./g)?.length || 0;
|
||||
return bLevel - aLevel;
|
||||
}
|
||||
return a.namespace.localeCompare(b.namespace);
|
||||
});
|
||||
|
||||
// add every messages from every namespace to an object
|
||||
const result: Record<string, string> = {};
|
||||
for (const extractedMessages of messagesByNamespace) {
|
||||
const { namespace, messages } = extractedMessages;
|
||||
for (const key of Object.keys(messages).sort()) {
|
||||
result[`${namespace}.${key}`] = messages[key];
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(result, null, ' ') + '\n';
|
||||
}
|
||||
|
||||
async function saveMessages() {
|
||||
const directoryPath = './src/';
|
||||
const resultPath = './src/i18n/locale/en.json';
|
||||
|
||||
const result = await processMessages(directoryPath);
|
||||
await fs.writeFile(resultPath, result);
|
||||
}
|
||||
|
||||
saveMessages();
|
||||
|
||||
export {};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
|
||||
const globalMessages = defineMessages({
|
||||
const globalMessages = defineMessages('i18n', {
|
||||
available: 'Available',
|
||||
partiallyavailable: 'Partially Available',
|
||||
processing: 'Processing',
|
||||
|
||||
@@ -616,13 +616,6 @@
|
||||
"components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL",
|
||||
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
|
||||
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration",
|
||||
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
|
||||
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!",
|
||||
"components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push notification settings failed to save.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push notification settings saved successfully!",
|
||||
"components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent",
|
||||
"components.Settings.Notifications.NotificationsWebhook.authheader": "Authorization Header",
|
||||
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload",
|
||||
@@ -638,6 +631,13 @@
|
||||
"components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL",
|
||||
"components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notification settings failed to save.",
|
||||
"components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook notification settings saved successfully!",
|
||||
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
|
||||
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
|
||||
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!",
|
||||
"components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push notification settings failed to save.",
|
||||
"components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push notification settings saved successfully!",
|
||||
"components.Settings.Notifications.agentenabled": "Enable Agent",
|
||||
"components.Settings.Notifications.allowselfsigned": "Allow Self-Signed Certificates",
|
||||
"components.Settings.Notifications.authPass": "SMTP Password",
|
||||
|
||||
Reference in New Issue
Block a user