locale.helper.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * i18n-aggregator.ts
  3. *
  4. * Aggregates translation files that match a list of prefixes.
  5. *
  6. */
  7. import { promises as fs } from "fs";
  8. import * as path from "path";
  9. import { factorioPath, scriptOutputPath } from "./file.helper.ts";
  10. import { dumpFactorioLocale } from "./command.helper.ts";
  11. type Translations = Record<string, Record<string, unknown>>;
  12. export const LOCALE_OF_INTEREST = ["fluid", "item", "recipe", "item-group", "quality", "virtual-signal"];
  13. export const LOCALE_TO_EXPORT = ["en", "ja", "zh-CH", "de", "fr"];
  14. /**
  15. * Aggregates i18n JSON files that match a list of prefixes and writes a single
  16. * JSON file containing all translations for the specified i18n code.
  17. *
  18. * @param prefixes Array of file prefixes to look for. Example: ['app', 'common']
  19. * @param inputFolder The folder to search for the locale files.
  20. * @param outputFolder The folder where the aggregated JSON will be written.
  21. * @param i18nCode The i18n code that will be used as the output file name.
  22. *
  23. * The function searches `inputFolder` for files that start with one of the
  24. * provided prefixes and end with `-locale.json`. Each matching file is
  25. * parsed as JSON and stored in a `Record<string, any>` where the key is the
  26. * prefix and the value is the parsed JSON object. The final aggregated
  27. * object is written to `outputFolder/i18n/<i18nCode>.json`.
  28. */
  29. export async function aggregateI18n(
  30. prefixes: string[],
  31. inputFolder: string,
  32. outputFolder: string,
  33. i18nCode: string
  34. ): Promise<void> {
  35. // Resolve absolute paths
  36. const inputPath = path.resolve(inputFolder);
  37. const outputPath = path.resolve(outputFolder, "i18n");
  38. // Ensure the output directory exists
  39. await fs.mkdir(outputPath, { recursive: true });
  40. // Read all files in the input directory
  41. const allFiles = await fs.readdir(inputPath);
  42. // Map of prefix => filename
  43. const prefixToFile: Record<string, string> = {};
  44. // Find the first file that matches each prefix
  45. for (const prefix of prefixes) {
  46. const filename = prefix + "-locale.json";
  47. const match = allFiles.find((f) => f == filename);
  48. if (match) {
  49. prefixToFile[prefix] = match;
  50. }
  51. }
  52. const result: Translations = {};
  53. // Read and parse each matched file
  54. for (const [prefix, fileName] of Object.entries(prefixToFile)) {
  55. const filePath = path.join(inputPath, fileName);
  56. const data = await fs.readFile(filePath, "utf-8");
  57. try {
  58. result[prefix] = JSON.parse(data);
  59. } catch {
  60. result[prefix] = {};
  61. }
  62. }
  63. // Write the aggregated JSON to the output folder
  64. const outputFile = path.join(outputPath, `${i18nCode}.json`);
  65. await fs.writeFile(outputFile, JSON.stringify(result, null, 2), "utf-8");
  66. }
  67. /**
  68. * Updates the first `locale=` line in a file to the supplied locale code.
  69. *
  70. * @param localCode New locale code (e.g. "en-US", "de-DE")
  71. *
  72. */
  73. export async function setLocaleInIni(localCode: string): Promise<void> {
  74. const resolvedPath = path.resolve(factorioPath, "config", "config.ini");
  75. const content = await fs.readFile(resolvedPath, "utf8");
  76. await fs.writeFile(resolvedPath, content.replace(/^locale=.*$/m, "locale=" + localCode), "utf8");
  77. }
  78. export async function extractLocale(outputFolder: string) {
  79. for (let i18nCode of LOCALE_TO_EXPORT) {
  80. console.log("Export i18n for " + i18nCode);
  81. await setLocaleInIni(i18nCode);
  82. await dumpFactorioLocale();
  83. await aggregateI18n(LOCALE_OF_INTEREST, scriptOutputPath, outputFolder, i18nCode);
  84. }
  85. }