// Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { parse } from 'csv-parse'; import fs from 'fs/promises'; import { z } from 'zod'; import { _getAvailableLocales } from '../../app/locale'; const type = process.argv[2]; if (type !== 'countries' && type !== 'locales') { throw new Error('Invalid first argument, expceted "countries" or "locales"'); } const localeDisplayNamesDataPath = process.argv[3]; if (!localeDisplayNamesDataPath) { throw new Error('Missing second argument: source csv file'); } const localeDisplayNamesBuildPath = process.argv[4]; if (!localeDisplayNamesBuildPath) { throw new Error('Missing third argument: output json file'); } const availableLocales = _getAvailableLocales(); const LocaleString = z.string().refine(arg => { try { return new Intl.Locale(arg) && true; } catch { return false; } }); const LocaleDisplayNames = z .tuple([ z .tuple([z.literal(type === 'locales' ? 'locale' : 'Country Code')]) .rest(LocaleString), ]) .rest(z.tuple([LocaleString]).rest(z.string())); type Row = ReadonlyArray; type Records = ReadonlyArray; function parseCsv(input: string) { return new Promise((resolve, reject) => { parse(input, { trim: true }, (error, records: Records) => { if (error) { reject(error); } else { resolve(records); } }); }); } type LocaleDisplayNamesResult = Record>; function convertData( input: z.infer ): LocaleDisplayNamesResult { const [[, ...keys], ...rows] = input; const result: LocaleDisplayNamesResult = {}; if (type === 'locales') { for (const row of rows) { const [subKey, ...messages] = row; result[subKey] = {}; for (const [index, message] of messages.entries()) { result[subKey][keys[index]] = message; } } } else { // Countries use transposed matrix. for (const key of keys) { result[key] = {}; } for (const row of rows) { const [subKey, ...messages] = row; for (const [index, message] of messages.entries()) { result[keys[index]][subKey] = message; } } } return result; } function assertValuesForAllLocales(result: LocaleDisplayNamesResult) { for (const locale of availableLocales) { const values = result[locale]; if (values == null) { throw new Error(`Missing values for locale ${locale}`); } for (const innerLocale of availableLocales) { if (values[innerLocale] == null) { throw new Error(`Missing value for locale ${locale} -> ${innerLocale}`); } } } } function assertValuesForAllCountries(result: LocaleDisplayNamesResult) { const availableCountries = Object.keys(result.en); for (const locale of availableLocales) { const values = result[locale]; if (values == null) { throw new Error(`Missing values for locale ${locale}`); } for (const country of availableCountries) { if (values[country] == null) { throw new Error(`Missing value for country ${locale} -> ${country}`); } } } } async function main() { const contents = await fs.readFile(localeDisplayNamesDataPath, 'utf-8'); const records = await parseCsv(contents); const data = LocaleDisplayNames.parse(records); const result = convertData(data); if (type === 'locales') { assertValuesForAllLocales(result); } else if (type === 'countries') { assertValuesForAllCountries(result); } const json = JSON.stringify(result, null, 2); await fs.writeFile(localeDisplayNamesBuildPath, json, 'utf-8'); } main().catch(error => { console.error(error); process.exit(1); });