@verbaly/compiler 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -189,9 +189,12 @@ declare function doctor(cfg: ResolvedConfig): Promise<DoctorResult>;
189
189
  //#endregion
190
190
  //#region src/exchange.d.ts
191
191
  type ExchangeFormat = 'xliff' | 'csv';
192
+ type MobileFormat = 'android-xml' | 'ios-strings';
193
+ type ExportFormat = ExchangeFormat | MobileFormat;
194
+ declare function isMobileFormat(format: ExportFormat): format is MobileFormat;
192
195
  interface ExportOptions {
193
196
  locales?: string[];
194
- format?: ExchangeFormat;
197
+ format?: ExportFormat;
195
198
  out?: string;
196
199
  missing?: boolean;
197
200
  }
@@ -202,7 +205,7 @@ interface ExportedFile {
202
205
  untranslated: number;
203
206
  }
204
207
  interface ExportResult {
205
- format: ExchangeFormat;
208
+ format: ExportFormat;
206
209
  dir: string;
207
210
  files: ExportedFile[];
208
211
  }
@@ -309,5 +312,5 @@ interface RenderSiteResult {
309
312
  }
310
313
  declare function renderSite(cfg: ResolvedConfig, options?: RenderSiteOptions): Promise<RenderSiteResult>;
311
314
  //#endregion
312
- export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, PSEUDO_LOCALE, type ParamType, type PluginOptions, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type TranslateConfig, type TranslateOptions, type TranslateProvider, type TranslateRequest, type TranslateResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
315
+ export { type Alternate, type Analysis, type AnalyzeOptions, type Catalog, type Catalogs, type CheckResult, type ClaudeProviderOptions, type DoctorEntry, type DoctorResult, type ExchangeFormat, type ExportFormat, type ExportOptions, type ExportResult, type ExportedFile, type ImportOptions, type ImportResult, type InitOptions, type InitResult, LOCALE_MODULE_PREFIX, MessageRegistry, type MissingEntry, type MobileFormat, PSEUDO_LOCALE, type ParamType, type PluginOptions, RESOLVED_VIRTUAL_ID, type RenderConfig, type RenderHtmlOptions, type RenderHtmlResult, type RenderSiteOptions, type RenderSiteResult, type ResolvedConfig, type RuntimeModuleOptions, SFC_FILE_RE, SOURCE_FILE_RE, type SyncResult, type TaggedMessage, type TaggedParam, type TransComponent, type TransformResult, type TranslateConfig, type TranslateOptions, type TranslateProvider, type TranslateRequest, type TranslateResult, type UnknownEntry, type UsedKey, VIRTUAL_ID, type VerbalyConfig, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
313
316
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -382,11 +382,17 @@ function catalogPath(cfg, locale) {
382
382
  return join(cfg.dir, `${locale}.json`);
383
383
  }
384
384
  function readCatalog(cfg, locale) {
385
+ let content;
385
386
  try {
386
- return JSON.parse(readFileSync(catalogPath(cfg, locale), "utf8"));
387
+ content = readFileSync(catalogPath(cfg, locale), "utf8");
387
388
  } catch {
388
389
  return {};
389
390
  }
391
+ try {
392
+ return JSON.parse(content.replace(/^\uFEFF/, ""));
393
+ } catch (error) {
394
+ throw new Error(`[verbaly] ${catalogPath(cfg, locale)} is not valid JSON, fix or delete the file`, { cause: error });
395
+ }
390
396
  }
391
397
  function loadCatalogs(cfg) {
392
398
  const catalogs = {};
@@ -1124,6 +1130,45 @@ function readDeps(root) {
1124
1130
  }
1125
1131
  }
1126
1132
  //#endregion
1133
+ //#region src/mobile.ts
1134
+ function androidValuesDir(locale, sourceLocale) {
1135
+ if (locale === sourceLocale) return "values";
1136
+ const parts = locale.split("-");
1137
+ if (parts.length === 1) return `values-${locale}`;
1138
+ if (parts.length === 2 && /^[a-zA-Z]{2}$/.test(parts[1])) return `values-${parts[0]}-r${parts[1].toUpperCase()}`;
1139
+ return `values-b+${parts.join("+")}`;
1140
+ }
1141
+ function toAndroidXml(entries) {
1142
+ const names = /* @__PURE__ */ new Map();
1143
+ return [
1144
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
1145
+ "<resources>",
1146
+ ...entries.map(({ key, text }) => {
1147
+ const name = androidName(key);
1148
+ const clash = names.get(name);
1149
+ if (clash !== void 0) throw new Error(`[verbaly] android-xml: keys "${clash}" and "${key}" both become resource name "${name}", rename one of them.`);
1150
+ names.set(name, key);
1151
+ return ` <string name="${name}">${androidText(text)}</string>`;
1152
+ }),
1153
+ "</resources>",
1154
+ ""
1155
+ ].join("\n");
1156
+ }
1157
+ function toIosStrings(entries) {
1158
+ return [...entries.map(({ key, text }) => `"${iosText(key)}" = "${iosText(text)}";`), ""].join("\n");
1159
+ }
1160
+ function androidName(key) {
1161
+ const name = key.replace(/[^A-Za-z0-9_]/g, "_");
1162
+ return /^[0-9]/.test(name) ? `_${name}` : name;
1163
+ }
1164
+ function androidText(text) {
1165
+ const escaped = text.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1166
+ return /^[@?]/.test(escaped) ? `\\${escaped}` : escaped;
1167
+ }
1168
+ function iosText(text) {
1169
+ return text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
1170
+ }
1171
+ //#endregion
1127
1172
  //#region src/translate.ts
1128
1173
  async function translateCatalogs(cfg, catalogs, provider, options = {}) {
1129
1174
  const batchSize = options.batchSize ?? 20;
@@ -1182,11 +1227,15 @@ function sameMembers(a, b) {
1182
1227
  }
1183
1228
  //#endregion
1184
1229
  //#region src/exchange.ts
1230
+ function isMobileFormat(format) {
1231
+ return format === "android-xml" || format === "ios-strings";
1232
+ }
1185
1233
  function exportCatalogs(cfg, catalogs, options = {}) {
1186
1234
  const format = options.format ?? "xliff";
1187
1235
  const dir = resolve(cfg.root, options.out ?? "verbaly-export");
1188
1236
  const source = catalogs[cfg.sourceLocale] ?? {};
1189
1237
  const targets = targetLocales(cfg, options.locales);
1238
+ if (isMobileFormat(format)) return exportMobile(cfg, catalogs, format, dir, source, targets);
1190
1239
  const files = [];
1191
1240
  mkdirSync(dir, { recursive: true });
1192
1241
  for (const locale of targets) {
@@ -1213,6 +1262,31 @@ function exportCatalogs(cfg, catalogs, options = {}) {
1213
1262
  files
1214
1263
  };
1215
1264
  }
1265
+ function exportMobile(cfg, catalogs, format, dir, source, targets) {
1266
+ const keys = Object.keys(source).filter((key) => source[key]).sort();
1267
+ const files = [];
1268
+ for (const locale of [cfg.sourceLocale, ...targets]) {
1269
+ const catalog = locale === cfg.sourceLocale ? source : catalogs[locale] ?? {};
1270
+ const entries = keys.map((key) => ({
1271
+ key,
1272
+ text: catalog[key] ?? ""
1273
+ })).filter((entry) => entry.text);
1274
+ const path = join(dir, format === "android-xml" ? join(androidValuesDir(locale, cfg.sourceLocale), "strings.xml") : join(`${locale}.lproj`, "Localizable.strings"));
1275
+ mkdirSync(dirname(path), { recursive: true });
1276
+ writeFileSync(path, format === "android-xml" ? toAndroidXml(entries) : toIosStrings(entries));
1277
+ files.push({
1278
+ locale,
1279
+ path,
1280
+ total: entries.length,
1281
+ untranslated: keys.length - entries.length
1282
+ });
1283
+ }
1284
+ return {
1285
+ format,
1286
+ dir,
1287
+ files
1288
+ };
1289
+ }
1216
1290
  function importCatalogs(cfg, catalogs, files, options = {}) {
1217
1291
  const source = catalogs[cfg.sourceLocale] ?? {};
1218
1292
  const result = {
@@ -1683,6 +1757,6 @@ function decodeEntities(text) {
1683
1757
  return text.replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
1684
1758
  }
1685
1759
  //#endregion
1686
- export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
1760
+ export { LOCALE_MODULE_PREFIX, MessageRegistry, PSEUDO_LOCALE, RESOLVED_VIRTUAL_ID, SFC_FILE_RE, SOURCE_FILE_RE, VIRTUAL_ID, analyze, analyzeFile, analyzeSfc, catalogPath, check, claudeProvider, collectParams, detectBundler, doctor, exportCatalogs, extractProject, findConfigFile, formatCheckResult, generateDts, generateLocaleModule, generateRuntimeModule, importCatalogs, init, isMobileFormat, isTransformTarget, loadCatalogs, loadConfig, loadConfigFile, loadVirtualModule, parseExchangeFile, pruneCatalogs, pseudoCatalogs, pseudoLocalize, readCatalog, renderHtml, renderParamType, renderSite, resolveConfig, resolveVirtualId, runBuildGate, serializeCatalog, stableKey, structureMatches, syncCatalogs, targetLocales, transformCode, transformSource, translateCatalogs, writeCatalog, writeDts };
1687
1761
 
1688
1762
  //# sourceMappingURL=index.js.map