feat: add language settings management

This commit is contained in:
poyrazavsever
2026-07-19 03:07:04 +03:00
parent 826410f53e
commit 5d3e720280
12 changed files with 1210 additions and 2 deletions
+125
View File
@@ -0,0 +1,125 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import Database from "better-sqlite3";
const dataDir = path.join(process.cwd(), ".data", `i18n-phase1-smoke-${Date.now()}`);
const databasePath = path.join(dataDir, "neta.db");
const restoreDir = `${dataDir}-restore`;
const env = { ...process.env, DATA_DIR: dataDir, DATABASE_PATH: databasePath };
fs.mkdirSync(dataDir, { recursive: true });
execFileSync(process.execPath, ["scripts/migrate.mjs"], {
cwd: process.cwd(),
env,
stdio: "inherit",
});
assertMigratedDatabase(databasePath, "tr");
execFileSync("./node_modules/.bin/tsc", ["-p", "tsconfig.i18n-phase1-smoke.json"], {
cwd: process.cwd(),
stdio: "inherit",
});
execFileSync(
process.execPath,
[path.join(".next", "i18n-phase1-smoke-dist", "scripts", "i18n-phase1-smoke.js"), databasePath],
{ cwd: process.cwd(), stdio: "inherit" },
);
execFileSync(process.execPath, ["scripts/backup.mjs", "--retention-count", "1"], {
cwd: process.cwd(),
env,
stdio: "inherit",
});
const backupDir = latestBackupDir(path.join(dataDir, "backups"));
execFileSync(
process.execPath,
["scripts/restore.mjs", "--from", backupDir, "--target", restoreDir],
{ cwd: process.cwd(), env: process.env, stdio: "inherit" },
);
assertMigratedDatabase(path.join(restoreDir, "neta.db"), "fr");
assertRestoredI18nData(path.join(restoreDir, "neta.db"));
console.log("I18n phase 1 migration, service and backup/restore smoke passed.");
function assertMigratedDatabase(dbPath, expectedDefaultLocale) {
const sqlite = new Database(dbPath);
try {
for (const tableName of [
"instance_locales",
"instance_i18n_settings",
"instance_ui_translations",
"content_translations",
]) {
assert.equal(
sqlite.prepare("select count(*) as value from sqlite_master where type = 'table' and name = ?").get(tableName).value,
1,
`${tableName} must exist`,
);
}
const builtInLocales = sqlite
.prepare("select code, status, fallback_locale as fallbackLocale, built_in as builtIn from instance_locales where code in ('tr', 'en') order by sort_order")
.all();
assert.deepEqual(
builtInLocales,
[
{ code: "tr", status: "active", fallbackLocale: null, builtIn: 1 },
{ code: "en", status: "active", fallbackLocale: "tr", builtIn: 1 },
],
"Built-in locales must be seeded",
);
assert.deepEqual(
sqlite.prepare("select key, default_locale as defaultLocale, catalog_version as catalogVersion from instance_i18n_settings").all(),
[{ key: "default", defaultLocale: expectedDefaultLocale, catalogVersion: 1 }],
"Default i18n settings must be seeded",
);
const clientColumns = sqlite.prepare("pragma table_info(clients)").all().map((column) => column.name);
assert.equal(clientColumns.includes("portal_locale"), true, "clients.portal_locale must exist");
const invitationColumns = sqlite.prepare("pragma table_info(portal_invitations)").all().map((column) => column.name);
assert.equal(invitationColumns.includes("locale"), true, "portal_invitations.locale must exist");
sqlite.prepare(
"insert into user (id, name, email, email_verified, created_at, updated_at) values (?, ?, ?, ?, ?, ?)",
).run("locale-preference-user", "Locale Preference User", "locale-preference@example.com", 1, Date.now(), Date.now());
sqlite.prepare("insert into user_preferences (owner_user_id, language) values (?, ?)").run("locale-preference-user", "fr");
sqlite.prepare("delete from user_preferences where owner_user_id = ?").run("locale-preference-user");
sqlite.prepare("delete from user where id = ?").run("locale-preference-user");
} finally {
sqlite.close();
}
}
function assertRestoredI18nData(dbPath) {
const sqlite = new Database(dbPath);
try {
assert.equal(
sqlite.prepare("select default_locale as value from instance_i18n_settings where key = 'default'").get().value,
"fr",
"Restored backup must include updated i18n settings",
);
assert.equal(
sqlite.prepare("select value from content_translations where locale = 'fr' and entity_id = 'i18n-project'").get().value,
"Projet multilingue",
"Restored backup must include content translations",
);
} finally {
sqlite.close();
}
}
function latestBackupDir(backupsDir) {
const entries = fs
.readdirSync(backupsDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(backupsDir, entry.name))
.sort((left, right) => path.basename(right).localeCompare(path.basename(left)));
assert.ok(entries[0], "Backup directory must contain a backup");
return entries[0];
}