Files
api/web/scripts/sort-translations.mjs
Eli Bosley 31c41027fc feat: translations now use crowdin (translate.unraid.net) (#1739)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- App-wide internationalization: dynamic locale detection/loading, many
new locale bundles, and CLI helpers to extract/sort translation keys.

- **Accessibility**
  - Brand button supports keyboard activation (Enter/Space).

- **Documentation**
  - Internationalization guidance added to API and Web READMEs.

- **Refactor**
- UI updated to use centralized i18n keys and a unified locale loading
approach.

- **Tests**
  - Test utilities updated to support i18n and localized assertions.

- **Chores**
- Crowdin config and i18n scripts added; runtime locale exposed for
selection.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-13 16:56:08 -04:00

60 lines
1.7 KiB
JavaScript

#!/usr/bin/env node
import { promises as fs } from 'fs';
import path from 'path';
import url from 'url';
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const LOCALES_DIR = path.join(__dirname, '..', 'src', 'locales');
// Create a shared collator for consistent sorting across machines and locales
const collator = new Intl.Collator('en', { sensitivity: 'base' });
function sortValue(value) {
if (Array.isArray(value)) {
return value.map(sortValue);
}
if (value && typeof value === 'object') {
return Object.keys(value)
.sort((a, b) => collator.compare(a, b))
.reduce((acc, key) => {
acc[key] = sortValue(value[key]);
return acc;
}, {});
}
return value;
}
async function sortLocaleFile(filePath) {
const original = await fs.readFile(filePath, 'utf8');
const parsed = JSON.parse(original);
const sorted = sortValue(parsed);
const normalized = JSON.stringify(sorted, null, 2) + '\n';
if (normalized !== original) {
await fs.writeFile(filePath, normalized, 'utf8');
return true;
}
return false;
}
async function main() {
const entries = await fs.readdir(LOCALES_DIR, { withFileTypes: true });
let changed = false;
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.json')) {
const localePath = path.join(LOCALES_DIR, entry.name);
const updated = await sortLocaleFile(localePath);
changed = changed || updated;
}
}
if (changed) {
console.log('[i18n] Sorted locale files.');
} else {
console.log('[i18n] Locale files already sorted.');
}
}
main().catch((error) => {
console.error('[i18n] Failed to sort locale files.', error);
process.exit(1);
});