@vocab/phrase 2.2.0 → 2.2.1

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.cjs CHANGED
@@ -1,4 +1,5 @@
1
- //#region rolldown:runtime
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -6,16 +7,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) {
13
- __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- }
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
19
16
  }
20
17
  return to;
21
18
  };
@@ -23,7 +20,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
20
  value: mod,
24
21
  enumerable: true
25
22
  }) : target, mod));
26
-
27
23
  //#endregion
28
24
  let fs = require("fs");
29
25
  let path = require("path");
@@ -34,18 +30,15 @@ picocolors = __toESM(picocolors);
34
30
  let debug = require("debug");
35
31
  debug = __toESM(debug);
36
32
  let csv_stringify_sync = require("csv-stringify/sync");
37
-
38
33
  //#region src/file.ts
39
34
  const mkdir = fs.promises.mkdir;
40
35
  const writeFile = fs.promises.writeFile;
41
-
42
36
  //#endregion
43
37
  //#region src/logger.ts
44
38
  const trace = (0, debug.default)(`vocab:phrase`);
45
39
  const log = (...params) => {
46
40
  console.log(picocolors.default.yellow("Vocab"), ...params);
47
41
  };
48
-
49
42
  //#endregion
50
43
  //#region src/csv.ts
51
44
  function translationsToCsv(translations, devLanguage) {
@@ -83,13 +76,12 @@ function translationsToCsv(translations, devLanguage) {
83
76
  tagColumn
84
77
  };
85
78
  }
86
-
87
79
  //#endregion
88
80
  //#region src/phrase-api.ts
89
- function _callPhrase(path$2, options = {}) {
81
+ function _callPhrase(path, options = {}) {
90
82
  const phraseApiToken = process.env.PHRASE_API_TOKEN;
91
83
  if (!phraseApiToken) throw new Error("Missing PHRASE_API_TOKEN");
92
- return fetch(path$2, {
84
+ return fetch(path, {
93
85
  ...options,
94
86
  headers: {
95
87
  Authorization: `token ${phraseApiToken}`,
@@ -97,7 +89,7 @@ function _callPhrase(path$2, options = {}) {
97
89
  ...options.headers
98
90
  }
99
91
  }).then(async (response) => {
100
- console.log(`${path$2}: ${response.status} - ${response.statusText}`);
92
+ console.log(`${path}: ${response.status} - ${response.statusText}`);
101
93
  const secondsUntilLimitReset = Math.ceil(Number.parseFloat(response.headers.get("X-Rate-Limit-Reset") || "0") - Date.now() / 1e3);
102
94
  console.log(`Rate Limit: ${response.headers.get("X-Rate-Limit-Remaining")} of ${response.headers.get("X-Rate-Limit-Limit")} remaining. (${secondsUntilLimitReset} seconds remaining)`);
103
95
  trace("\nLink:", response.headers.get("Link"), "\n");
@@ -192,7 +184,6 @@ async function ensureBranch(branch) {
192
184
  });
193
185
  log("Created branch:", branch);
194
186
  }
195
-
196
187
  //#endregion
197
188
  //#region src/pull-translations.ts
198
189
  async function pull({ branch = "local-development", errorOnNoGlobalKeyTranslation }, config) {
@@ -242,7 +233,6 @@ async function pull({ branch = "local-development", errorOnNoGlobalKeyTranslatio
242
233
  }
243
234
  }
244
235
  }
245
-
246
236
  //#endregion
247
237
  //#region src/push-translations.ts
248
238
  /**
@@ -283,8 +273,8 @@ async function push({ autoTranslate, branch, deleteUnusedKeys: deleteUnusedKeys$
283
273
  });
284
274
  if (deleteUnusedKeys$1) await deleteUnusedKeys(devLanguageUploadId, branch);
285
275
  }
286
-
287
276
  //#endregion
288
277
  exports.pull = pull;
289
278
  exports.push = push;
279
+
290
280
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["pc","csvFilesByLanguage: Record<LanguageName, CsvFile>","path","translations: TranslationsByLanguage","defaultValues: TranslationFileContents","phraseTranslations: TranslationsByLanguage","deleteUnusedKeys","phraseDeleteUnusedKeys"],"sources":["../src/file.ts","../src/logger.ts","../src/csv.ts","../src/phrase-api.ts","../src/pull-translations.ts","../src/push-translations.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nexport const mkdir = fs.mkdir;\nexport const writeFile = fs.writeFile;\n","import pc from 'picocolors';\nimport debug from 'debug';\n\nexport const trace = debug(`vocab:phrase`);\n\nexport const log = (...params: unknown[]) => {\n // eslint-disable-next-line no-console\n console.log(pc.yellow('Vocab'), ...params);\n};\n","import { stringify } from 'csv-stringify/sync';\nimport type { LanguageName, TranslationsByLanguage } from '@vocab/core';\n\ntype Value = string | undefined;\ntype CsvRow = Value[];\ntype CsvFile = CsvRow[];\n\nexport function translationsToCsv(\n translations: TranslationsByLanguage,\n devLanguage: string,\n) {\n const languages = Object.keys(translations);\n const altLanguages = languages.filter((language) => language !== devLanguage);\n\n const devLanguageTranslations = translations[devLanguage];\n\n const csvFilesByLanguage: Record<LanguageName, CsvFile> = Object.fromEntries(\n languages.map((language) => [language, []]),\n );\n\n Object.entries(devLanguageTranslations).map(\n ([key, { message, description, tags }]) => {\n const sharedData = [key, description, tags?.join(',')];\n const devLanguageRow = [...sharedData, message];\n csvFilesByLanguage[devLanguage].push(devLanguageRow);\n\n altLanguages.map((language) => {\n const altTranslationMessage = translations[language]?.[key]?.message;\n\n if (altTranslationMessage) {\n csvFilesByLanguage[language].push([\n ...sharedData,\n altTranslationMessage,\n ]);\n }\n });\n },\n );\n\n const csvFileStrings = Object.fromEntries(\n Object.entries(csvFilesByLanguage)\n // Ensure CSV files are only created if the language has at least 1 translation\n .filter(([_, csvFile]) => csvFile.length > 0)\n .map(([language, csvFile]) => {\n const csvFileString = stringify(csvFile, {\n delimiter: ',',\n header: false,\n });\n\n return [language, csvFileString];\n }),\n );\n\n // Column indices start at 1\n const keyIndex = 1;\n const commentIndex = keyIndex + 1;\n const tagColumn = commentIndex + 1;\n const messageIndex = tagColumn + 1;\n\n return { csvFileStrings, keyIndex, messageIndex, commentIndex, tagColumn };\n}\n","/* eslint-disable no-console */\nimport type { TranslationsByLanguage } from '@vocab/core';\nimport { log, trace } from './logger';\nimport { translationsToCsv } from './csv';\n\nfunction _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {\n const phraseApiToken = process.env.PHRASE_API_TOKEN;\n\n if (!phraseApiToken) {\n throw new Error('Missing PHRASE_API_TOKEN');\n }\n\n return fetch(path, {\n ...options,\n headers: {\n Authorization: `token ${phraseApiToken}`,\n // Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent\n 'User-Agent': 'Vocab Client (https://github.com/seek-oss/vocab)',\n ...options.headers,\n },\n }).then(async (response) => {\n console.log(`${path}: ${response.status} - ${response.statusText}`);\n\n const secondsUntilLimitReset = Math.ceil(\n Number.parseFloat(response.headers.get('X-Rate-Limit-Reset') || '0') -\n Date.now() / 1000,\n );\n console.log(\n `Rate Limit: ${response.headers.get(\n 'X-Rate-Limit-Remaining',\n )} of ${response.headers.get(\n 'X-Rate-Limit-Limit',\n )} remaining. (${secondsUntilLimitReset} seconds remaining)`,\n );\n\n trace('\\nLink:', response.headers.get('Link'), '\\n');\n // Print All Headers:\n // console.log(Array.from(r.headers.entries()));\n\n try {\n const result = await response.json();\n\n trace(`Internal Result (Length: ${result.length})\\n`);\n\n if (\n (!options.method || options.method === 'GET') &&\n response.headers.get('Link')?.includes('rel=next')\n ) {\n const [, nextPageUrl] =\n response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];\n\n if (!nextPageUrl) {\n throw new Error(\"Can't parse next page URL\");\n }\n\n console.log('Results received with next page: ', nextPageUrl);\n\n const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;\n\n return [...result, ...nextPageResult];\n }\n\n return result;\n } catch (e) {\n console.error('Unable to parse response as JSON', e);\n return response.text();\n }\n });\n}\n\nexport async function callPhrase<T = any>(\n relativePath: string,\n options: Parameters<typeof fetch>[1] = {},\n): Promise<T> {\n const projectId = process.env.PHRASE_PROJECT_ID;\n\n if (!projectId) {\n throw new Error('Missing PHRASE_PROJECT_ID');\n }\n return _callPhrase(\n `https://api.phrase.com/v2/projects/${projectId}/${relativePath}`,\n options,\n )\n .then((result) => {\n if (Array.isArray(result)) {\n console.log('Result length:', result.length);\n }\n return result;\n })\n .catch((error) => {\n console.error(`Error calling phrase for ${relativePath}:`, error);\n throw Error;\n });\n}\n\nexport async function pullAllTranslations(\n branch: string,\n): Promise<TranslationsByLanguage> {\n const phraseResult = await callPhrase<\n Array<{\n key: { name: string };\n locale: { name: string };\n content: string;\n }>\n >(`translations?branch=${branch}&per_page=100`);\n\n const translations: TranslationsByLanguage = {};\n\n for (const r of phraseResult) {\n if (!translations[r.locale.name]) {\n translations[r.locale.name] = {};\n }\n translations[r.locale.name][r.key.name] = { message: r.content };\n }\n\n return translations;\n}\n\nexport async function pushTranslations(\n translationsByLanguage: TranslationsByLanguage,\n {\n autoTranslate,\n branch,\n devLanguage,\n }: { autoTranslate?: boolean; branch: string; devLanguage: string },\n) {\n const { csvFileStrings, keyIndex, commentIndex, tagColumn, messageIndex } =\n translationsToCsv(translationsByLanguage, devLanguage);\n\n let devLanguageUploadId = '';\n\n for (const [language, csvFileString] of Object.entries(csvFileStrings)) {\n const formData = new FormData();\n\n formData.append(\n 'file',\n new Blob([csvFileString], {\n type: 'text/csv',\n }),\n `${language}.translations.csv`,\n );\n\n formData.append('file_format', 'csv');\n formData.append('branch', branch);\n formData.append('update_translations', 'true');\n formData.append('update_descriptions', 'true');\n\n if (autoTranslate) {\n formData.append('autotranslate', 'true');\n }\n\n formData.append(`locale_mapping[${language}]`, messageIndex.toString());\n\n formData.append('format_options[key_index]', keyIndex.toString());\n formData.append('format_options[comment_index]', commentIndex.toString());\n formData.append('format_options[tag_column]', tagColumn.toString());\n formData.append('format_options[enable_pluralization]', 'false');\n\n log(`Uploading translations for language ${language}`);\n\n const result = await callPhrase<\n | {\n id: string;\n }\n | {\n message: string;\n errors: unknown[];\n }\n | undefined\n >(`uploads`, {\n method: 'POST',\n body: formData,\n });\n\n trace('Upload result:\\n', result);\n\n if (result && 'id' in result) {\n log('Upload ID:', result.id, '\\n');\n log('Successfully Uploaded\\n');\n } else {\n log(`Error uploading: ${result?.message}\\n`);\n log('Response:', result);\n throw new Error('Error uploading');\n }\n\n if (language === devLanguage) {\n devLanguageUploadId = result.id;\n }\n }\n\n return {\n devLanguageUploadId,\n };\n}\n\nexport async function deleteUnusedKeys(uploadId: string, branch: string) {\n const query = `unmentioned_in_upload:${uploadId}`;\n const { records_affected } = await callPhrase<{ records_affected: number }>(\n 'keys',\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ branch, q: query }),\n },\n );\n\n log(\n 'Successfully deleted',\n records_affected,\n 'unused keys from branch',\n branch,\n );\n}\n\nexport async function ensureBranch(branch: string) {\n await callPhrase(`branches`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: branch }),\n });\n\n log('Created branch:', branch);\n}\n","import { writeFile, mkdir } from './file';\nimport path from 'path';\n\nimport {\n type TranslationFileContents,\n type UserConfig,\n loadAllTranslations,\n getAltLanguageFilePath,\n getAltLanguages,\n getUniqueKey,\n} from '@vocab/core';\n\nimport { pullAllTranslations, ensureBranch } from './phrase-api';\nimport { trace } from './logger';\n\ninterface PullOptions {\n branch?: string;\n deleteUnusedKeys?: boolean;\n errorOnNoGlobalKeyTranslation?: boolean;\n}\n\nexport async function pull(\n { branch = 'local-development', errorOnNoGlobalKeyTranslation }: PullOptions,\n config: UserConfig,\n) {\n trace(`Pulling translations from branch ${branch}`);\n await ensureBranch(branch);\n const alternativeLanguages = getAltLanguages(config);\n const allPhraseTranslations = await pullAllTranslations(branch);\n trace(\n `Pulling translations from Phrase for languages ${\n config.devLanguage\n } and ${alternativeLanguages.join(', ')}`,\n );\n\n const phraseLanguages = Object.keys(allPhraseTranslations);\n trace(\n `Found Phrase translations for languages ${phraseLanguages.join(', ')}`,\n );\n\n if (!phraseLanguages.includes(config.devLanguage)) {\n throw new Error(\n `Phrase did not return any translations for the configured development language \"${config.devLanguage}\".\\nPlease ensure this language is present in your Phrase project's configuration.`,\n );\n }\n\n const allVocabTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n config,\n );\n\n for (const loadedTranslation of allVocabTranslations) {\n const devTranslations = loadedTranslation.languages[config.devLanguage];\n\n if (!devTranslations) {\n throw new Error('No dev language translations loaded');\n }\n\n const defaultValues: TranslationFileContents = { ...devTranslations };\n const localKeys = Object.keys(defaultValues);\n\n for (const key of localKeys) {\n defaultValues[key] = {\n ...defaultValues[key],\n ...allPhraseTranslations[config.devLanguage][\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace)\n ],\n };\n }\n\n // Only write a `_meta` field if necessary\n if (Object.keys(loadedTranslation.metadata).length > 0) {\n defaultValues._meta = loadedTranslation.metadata;\n }\n\n await writeFile(\n loadedTranslation.filePath,\n `${JSON.stringify(defaultValues, null, 2)}\\n`,\n );\n\n for (const alternativeLanguage of alternativeLanguages) {\n if (alternativeLanguage in allPhraseTranslations) {\n const altTranslations = {\n ...loadedTranslation.languages[alternativeLanguage],\n };\n const phraseAltTranslations =\n allPhraseTranslations[alternativeLanguage];\n\n for (const key of localKeys) {\n const phraseKey =\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace);\n const phraseTranslationMessage =\n phraseAltTranslations[phraseKey]?.message;\n\n if (!phraseTranslationMessage) {\n trace(\n `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,\n );\n if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {\n throw new Error(\n `Missing translation for global key ${key} in language ${alternativeLanguage}`,\n );\n }\n continue;\n }\n\n altTranslations[key] = {\n ...altTranslations[key],\n message: phraseTranslationMessage,\n };\n }\n\n const altTranslationFilePath = getAltLanguageFilePath(\n loadedTranslation.filePath,\n alternativeLanguage,\n );\n\n await mkdir(path.dirname(altTranslationFilePath), {\n recursive: true,\n });\n await writeFile(\n altTranslationFilePath,\n `${JSON.stringify(altTranslations, null, 2)}\\n`,\n );\n }\n }\n }\n}\n","import {\n loadAllTranslations,\n getUniqueKey,\n type TranslationData,\n type TranslationsByLanguage,\n type UserConfig,\n} from '@vocab/core';\nimport {\n ensureBranch,\n deleteUnusedKeys as phraseDeleteUnusedKeys,\n pushTranslations,\n} from './phrase-api';\nimport { trace } from './logger';\n\ninterface PushOptions {\n autoTranslate?: boolean;\n branch: string;\n deleteUnusedKeys?: boolean;\n ignore?: string[];\n}\n\n/**\n * Uploads translations to the Phrase API for each language.\n * A unique namespace is appended to each key using the file path the key came from.\n */\nexport async function push(\n { autoTranslate, branch, deleteUnusedKeys, ignore }: PushOptions,\n config: UserConfig,\n) {\n if (ignore) {\n trace(`ignoring files on paths: ${ignore.join(', ')}`);\n }\n const allLanguageTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n {\n ...config,\n ignore: [...(config.ignore || []), ...(ignore || [])],\n },\n );\n trace(`Pushing translations to branch ${branch}`);\n const allLanguages = config.languages.map((v) => v.name);\n await ensureBranch(branch);\n\n trace(\n `Pushing translations to phrase for languages ${allLanguages.join(', ')}`,\n );\n\n const phraseTranslations: TranslationsByLanguage = {};\n\n for (const loadedTranslation of allLanguageTranslations) {\n for (const language of allLanguages) {\n const localTranslations = loadedTranslation.languages[language];\n if (!localTranslations) {\n continue;\n }\n if (!phraseTranslations[language]) {\n phraseTranslations[language] = {};\n }\n\n const {\n metadata: { tags: sharedTags = [] },\n } = loadedTranslation;\n\n for (const localKey of Object.keys(localTranslations)) {\n const { tags = [], ...localTranslation } = localTranslations[localKey];\n if (language === config.devLanguage) {\n (localTranslation as TranslationData).tags = [...tags, ...sharedTags];\n }\n const globalKey =\n loadedTranslation.languages[config.devLanguage][localKey].globalKey;\n\n const phraseKey =\n globalKey ?? getUniqueKey(localKey, loadedTranslation.namespace);\n\n phraseTranslations[language][phraseKey] = localTranslation;\n }\n }\n }\n\n const { devLanguageUploadId } = await pushTranslations(phraseTranslations, {\n autoTranslate,\n branch,\n devLanguage: config.devLanguage,\n });\n\n if (deleteUnusedKeys) {\n await phraseDeleteUnusedKeys(devLanguageUploadId, branch);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["pc","deleteUnusedKeys","phraseDeleteUnusedKeys"],"sources":["../src/file.ts","../src/logger.ts","../src/csv.ts","../src/phrase-api.ts","../src/pull-translations.ts","../src/push-translations.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nexport const mkdir = fs.mkdir;\nexport const writeFile = fs.writeFile;\n","import pc from 'picocolors';\nimport debug from 'debug';\n\nexport const trace = debug(`vocab:phrase`);\n\nexport const log = (...params: unknown[]) => {\n // eslint-disable-next-line no-console\n console.log(pc.yellow('Vocab'), ...params);\n};\n","import { stringify } from 'csv-stringify/sync';\nimport type { LanguageName, TranslationsByLanguage } from '@vocab/core';\n\ntype Value = string | undefined;\ntype CsvRow = Value[];\ntype CsvFile = CsvRow[];\n\nexport function translationsToCsv(\n translations: TranslationsByLanguage,\n devLanguage: string,\n) {\n const languages = Object.keys(translations);\n const altLanguages = languages.filter((language) => language !== devLanguage);\n\n const devLanguageTranslations = translations[devLanguage];\n\n const csvFilesByLanguage: Record<LanguageName, CsvFile> = Object.fromEntries(\n languages.map((language) => [language, []]),\n );\n\n Object.entries(devLanguageTranslations).map(\n ([key, { message, description, tags }]) => {\n const sharedData = [key, description, tags?.join(',')];\n const devLanguageRow = [...sharedData, message];\n csvFilesByLanguage[devLanguage].push(devLanguageRow);\n\n altLanguages.map((language) => {\n const altTranslationMessage = translations[language]?.[key]?.message;\n\n if (altTranslationMessage) {\n csvFilesByLanguage[language].push([\n ...sharedData,\n altTranslationMessage,\n ]);\n }\n });\n },\n );\n\n const csvFileStrings = Object.fromEntries(\n Object.entries(csvFilesByLanguage)\n // Ensure CSV files are only created if the language has at least 1 translation\n .filter(([_, csvFile]) => csvFile.length > 0)\n .map(([language, csvFile]) => {\n const csvFileString = stringify(csvFile, {\n delimiter: ',',\n header: false,\n });\n\n return [language, csvFileString];\n }),\n );\n\n // Column indices start at 1\n const keyIndex = 1;\n const commentIndex = keyIndex + 1;\n const tagColumn = commentIndex + 1;\n const messageIndex = tagColumn + 1;\n\n return { csvFileStrings, keyIndex, messageIndex, commentIndex, tagColumn };\n}\n","/* eslint-disable no-console */\nimport type { TranslationsByLanguage } from '@vocab/core';\nimport { log, trace } from './logger';\nimport { translationsToCsv } from './csv';\n\nfunction _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {\n const phraseApiToken = process.env.PHRASE_API_TOKEN;\n\n if (!phraseApiToken) {\n throw new Error('Missing PHRASE_API_TOKEN');\n }\n\n return fetch(path, {\n ...options,\n headers: {\n Authorization: `token ${phraseApiToken}`,\n // Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent\n 'User-Agent': 'Vocab Client (https://github.com/seek-oss/vocab)',\n ...options.headers,\n },\n }).then(async (response) => {\n console.log(`${path}: ${response.status} - ${response.statusText}`);\n\n const secondsUntilLimitReset = Math.ceil(\n Number.parseFloat(response.headers.get('X-Rate-Limit-Reset') || '0') -\n Date.now() / 1000,\n );\n console.log(\n `Rate Limit: ${response.headers.get(\n 'X-Rate-Limit-Remaining',\n )} of ${response.headers.get(\n 'X-Rate-Limit-Limit',\n )} remaining. (${secondsUntilLimitReset} seconds remaining)`,\n );\n\n trace('\\nLink:', response.headers.get('Link'), '\\n');\n // Print All Headers:\n // console.log(Array.from(r.headers.entries()));\n\n try {\n const result = await response.json();\n\n trace(`Internal Result (Length: ${result.length})\\n`);\n\n if (\n (!options.method || options.method === 'GET') &&\n response.headers.get('Link')?.includes('rel=next')\n ) {\n const [, nextPageUrl] =\n response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];\n\n if (!nextPageUrl) {\n throw new Error(\"Can't parse next page URL\");\n }\n\n console.log('Results received with next page: ', nextPageUrl);\n\n const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;\n\n return [...result, ...nextPageResult];\n }\n\n return result;\n } catch (e) {\n console.error('Unable to parse response as JSON', e);\n return response.text();\n }\n });\n}\n\nexport async function callPhrase<T = any>(\n relativePath: string,\n options: Parameters<typeof fetch>[1] = {},\n): Promise<T> {\n const projectId = process.env.PHRASE_PROJECT_ID;\n\n if (!projectId) {\n throw new Error('Missing PHRASE_PROJECT_ID');\n }\n return _callPhrase(\n `https://api.phrase.com/v2/projects/${projectId}/${relativePath}`,\n options,\n )\n .then((result) => {\n if (Array.isArray(result)) {\n console.log('Result length:', result.length);\n }\n return result;\n })\n .catch((error) => {\n console.error(`Error calling phrase for ${relativePath}:`, error);\n throw Error;\n });\n}\n\nexport async function pullAllTranslations(\n branch: string,\n): Promise<TranslationsByLanguage> {\n const phraseResult = await callPhrase<\n Array<{\n key: { name: string };\n locale: { name: string };\n content: string;\n }>\n >(`translations?branch=${branch}&per_page=100`);\n\n const translations: TranslationsByLanguage = {};\n\n for (const r of phraseResult) {\n if (!translations[r.locale.name]) {\n translations[r.locale.name] = {};\n }\n translations[r.locale.name][r.key.name] = { message: r.content };\n }\n\n return translations;\n}\n\nexport async function pushTranslations(\n translationsByLanguage: TranslationsByLanguage,\n {\n autoTranslate,\n branch,\n devLanguage,\n }: { autoTranslate?: boolean; branch: string; devLanguage: string },\n) {\n const { csvFileStrings, keyIndex, commentIndex, tagColumn, messageIndex } =\n translationsToCsv(translationsByLanguage, devLanguage);\n\n let devLanguageUploadId = '';\n\n for (const [language, csvFileString] of Object.entries(csvFileStrings)) {\n const formData = new FormData();\n\n formData.append(\n 'file',\n new Blob([csvFileString], {\n type: 'text/csv',\n }),\n `${language}.translations.csv`,\n );\n\n formData.append('file_format', 'csv');\n formData.append('branch', branch);\n formData.append('update_translations', 'true');\n formData.append('update_descriptions', 'true');\n\n if (autoTranslate) {\n formData.append('autotranslate', 'true');\n }\n\n formData.append(`locale_mapping[${language}]`, messageIndex.toString());\n\n formData.append('format_options[key_index]', keyIndex.toString());\n formData.append('format_options[comment_index]', commentIndex.toString());\n formData.append('format_options[tag_column]', tagColumn.toString());\n formData.append('format_options[enable_pluralization]', 'false');\n\n log(`Uploading translations for language ${language}`);\n\n const result = await callPhrase<\n | {\n id: string;\n }\n | {\n message: string;\n errors: unknown[];\n }\n | undefined\n >(`uploads`, {\n method: 'POST',\n body: formData,\n });\n\n trace('Upload result:\\n', result);\n\n if (result && 'id' in result) {\n log('Upload ID:', result.id, '\\n');\n log('Successfully Uploaded\\n');\n } else {\n log(`Error uploading: ${result?.message}\\n`);\n log('Response:', result);\n throw new Error('Error uploading');\n }\n\n if (language === devLanguage) {\n devLanguageUploadId = result.id;\n }\n }\n\n return {\n devLanguageUploadId,\n };\n}\n\nexport async function deleteUnusedKeys(uploadId: string, branch: string) {\n const query = `unmentioned_in_upload:${uploadId}`;\n const { records_affected } = await callPhrase<{ records_affected: number }>(\n 'keys',\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ branch, q: query }),\n },\n );\n\n log(\n 'Successfully deleted',\n records_affected,\n 'unused keys from branch',\n branch,\n );\n}\n\nexport async function ensureBranch(branch: string) {\n await callPhrase(`branches`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: branch }),\n });\n\n log('Created branch:', branch);\n}\n","import { writeFile, mkdir } from './file';\nimport path from 'path';\n\nimport {\n type TranslationFileContents,\n type UserConfig,\n loadAllTranslations,\n getAltLanguageFilePath,\n getAltLanguages,\n getUniqueKey,\n} from '@vocab/core';\n\nimport { pullAllTranslations, ensureBranch } from './phrase-api';\nimport { trace } from './logger';\n\ninterface PullOptions {\n branch?: string;\n deleteUnusedKeys?: boolean;\n errorOnNoGlobalKeyTranslation?: boolean;\n}\n\nexport async function pull(\n { branch = 'local-development', errorOnNoGlobalKeyTranslation }: PullOptions,\n config: UserConfig,\n) {\n trace(`Pulling translations from branch ${branch}`);\n await ensureBranch(branch);\n const alternativeLanguages = getAltLanguages(config);\n const allPhraseTranslations = await pullAllTranslations(branch);\n trace(\n `Pulling translations from Phrase for languages ${\n config.devLanguage\n } and ${alternativeLanguages.join(', ')}`,\n );\n\n const phraseLanguages = Object.keys(allPhraseTranslations);\n trace(\n `Found Phrase translations for languages ${phraseLanguages.join(', ')}`,\n );\n\n if (!phraseLanguages.includes(config.devLanguage)) {\n throw new Error(\n `Phrase did not return any translations for the configured development language \"${config.devLanguage}\".\\nPlease ensure this language is present in your Phrase project's configuration.`,\n );\n }\n\n const allVocabTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n config,\n );\n\n for (const loadedTranslation of allVocabTranslations) {\n const devTranslations = loadedTranslation.languages[config.devLanguage];\n\n if (!devTranslations) {\n throw new Error('No dev language translations loaded');\n }\n\n const defaultValues: TranslationFileContents = { ...devTranslations };\n const localKeys = Object.keys(defaultValues);\n\n for (const key of localKeys) {\n defaultValues[key] = {\n ...defaultValues[key],\n ...allPhraseTranslations[config.devLanguage][\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace)\n ],\n };\n }\n\n // Only write a `_meta` field if necessary\n if (Object.keys(loadedTranslation.metadata).length > 0) {\n defaultValues._meta = loadedTranslation.metadata;\n }\n\n await writeFile(\n loadedTranslation.filePath,\n `${JSON.stringify(defaultValues, null, 2)}\\n`,\n );\n\n for (const alternativeLanguage of alternativeLanguages) {\n if (alternativeLanguage in allPhraseTranslations) {\n const altTranslations = {\n ...loadedTranslation.languages[alternativeLanguage],\n };\n const phraseAltTranslations =\n allPhraseTranslations[alternativeLanguage];\n\n for (const key of localKeys) {\n const phraseKey =\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace);\n const phraseTranslationMessage =\n phraseAltTranslations[phraseKey]?.message;\n\n if (!phraseTranslationMessage) {\n trace(\n `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,\n );\n if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {\n throw new Error(\n `Missing translation for global key ${key} in language ${alternativeLanguage}`,\n );\n }\n continue;\n }\n\n altTranslations[key] = {\n ...altTranslations[key],\n message: phraseTranslationMessage,\n };\n }\n\n const altTranslationFilePath = getAltLanguageFilePath(\n loadedTranslation.filePath,\n alternativeLanguage,\n );\n\n await mkdir(path.dirname(altTranslationFilePath), {\n recursive: true,\n });\n await writeFile(\n altTranslationFilePath,\n `${JSON.stringify(altTranslations, null, 2)}\\n`,\n );\n }\n }\n }\n}\n","import {\n loadAllTranslations,\n getUniqueKey,\n type TranslationData,\n type TranslationsByLanguage,\n type UserConfig,\n} from '@vocab/core';\nimport {\n ensureBranch,\n deleteUnusedKeys as phraseDeleteUnusedKeys,\n pushTranslations,\n} from './phrase-api';\nimport { trace } from './logger';\n\ninterface PushOptions {\n autoTranslate?: boolean;\n branch: string;\n deleteUnusedKeys?: boolean;\n ignore?: string[];\n}\n\n/**\n * Uploads translations to the Phrase API for each language.\n * A unique namespace is appended to each key using the file path the key came from.\n */\nexport async function push(\n { autoTranslate, branch, deleteUnusedKeys, ignore }: PushOptions,\n config: UserConfig,\n) {\n if (ignore) {\n trace(`ignoring files on paths: ${ignore.join(', ')}`);\n }\n const allLanguageTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n {\n ...config,\n ignore: [...(config.ignore || []), ...(ignore || [])],\n },\n );\n trace(`Pushing translations to branch ${branch}`);\n const allLanguages = config.languages.map((v) => v.name);\n await ensureBranch(branch);\n\n trace(\n `Pushing translations to phrase for languages ${allLanguages.join(', ')}`,\n );\n\n const phraseTranslations: TranslationsByLanguage = {};\n\n for (const loadedTranslation of allLanguageTranslations) {\n for (const language of allLanguages) {\n const localTranslations = loadedTranslation.languages[language];\n if (!localTranslations) {\n continue;\n }\n if (!phraseTranslations[language]) {\n phraseTranslations[language] = {};\n }\n\n const {\n metadata: { tags: sharedTags = [] },\n } = loadedTranslation;\n\n for (const localKey of Object.keys(localTranslations)) {\n const { tags = [], ...localTranslation } = localTranslations[localKey];\n if (language === config.devLanguage) {\n (localTranslation as TranslationData).tags = [...tags, ...sharedTags];\n }\n const globalKey =\n loadedTranslation.languages[config.devLanguage][localKey].globalKey;\n\n const phraseKey =\n globalKey ?? getUniqueKey(localKey, loadedTranslation.namespace);\n\n phraseTranslations[language][phraseKey] = localTranslation;\n }\n }\n }\n\n const { devLanguageUploadId } = await pushTranslations(phraseTranslations, {\n autoTranslate,\n branch,\n devLanguage: config.devLanguage,\n });\n\n if (deleteUnusedKeys) {\n await phraseDeleteUnusedKeys(devLanguageUploadId, branch);\n }\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -4,18 +4,15 @@ import { getAltLanguageFilePath, getAltLanguages, getUniqueKey, loadAllTranslati
4
4
  import pc from "picocolors";
5
5
  import debug from "debug";
6
6
  import { stringify } from "csv-stringify/sync";
7
-
8
7
  //#region src/file.ts
9
8
  const mkdir = promises.mkdir;
10
9
  const writeFile = promises.writeFile;
11
-
12
10
  //#endregion
13
11
  //#region src/logger.ts
14
12
  const trace = debug(`vocab:phrase`);
15
13
  const log = (...params) => {
16
14
  console.log(pc.yellow("Vocab"), ...params);
17
15
  };
18
-
19
16
  //#endregion
20
17
  //#region src/csv.ts
21
18
  function translationsToCsv(translations, devLanguage) {
@@ -53,13 +50,12 @@ function translationsToCsv(translations, devLanguage) {
53
50
  tagColumn
54
51
  };
55
52
  }
56
-
57
53
  //#endregion
58
54
  //#region src/phrase-api.ts
59
- function _callPhrase(path$1, options = {}) {
55
+ function _callPhrase(path, options = {}) {
60
56
  const phraseApiToken = process.env.PHRASE_API_TOKEN;
61
57
  if (!phraseApiToken) throw new Error("Missing PHRASE_API_TOKEN");
62
- return fetch(path$1, {
58
+ return fetch(path, {
63
59
  ...options,
64
60
  headers: {
65
61
  Authorization: `token ${phraseApiToken}`,
@@ -67,7 +63,7 @@ function _callPhrase(path$1, options = {}) {
67
63
  ...options.headers
68
64
  }
69
65
  }).then(async (response) => {
70
- console.log(`${path$1}: ${response.status} - ${response.statusText}`);
66
+ console.log(`${path}: ${response.status} - ${response.statusText}`);
71
67
  const secondsUntilLimitReset = Math.ceil(Number.parseFloat(response.headers.get("X-Rate-Limit-Reset") || "0") - Date.now() / 1e3);
72
68
  console.log(`Rate Limit: ${response.headers.get("X-Rate-Limit-Remaining")} of ${response.headers.get("X-Rate-Limit-Limit")} remaining. (${secondsUntilLimitReset} seconds remaining)`);
73
69
  trace("\nLink:", response.headers.get("Link"), "\n");
@@ -162,7 +158,6 @@ async function ensureBranch(branch) {
162
158
  });
163
159
  log("Created branch:", branch);
164
160
  }
165
-
166
161
  //#endregion
167
162
  //#region src/pull-translations.ts
168
163
  async function pull({ branch = "local-development", errorOnNoGlobalKeyTranslation }, config) {
@@ -212,7 +207,6 @@ async function pull({ branch = "local-development", errorOnNoGlobalKeyTranslatio
212
207
  }
213
208
  }
214
209
  }
215
-
216
210
  //#endregion
217
211
  //#region src/push-translations.ts
218
212
  /**
@@ -253,7 +247,7 @@ async function push({ autoTranslate, branch, deleteUnusedKeys: deleteUnusedKeys$
253
247
  });
254
248
  if (deleteUnusedKeys$1) await deleteUnusedKeys(devLanguageUploadId, branch);
255
249
  }
256
-
257
250
  //#endregion
258
251
  export { pull, push };
252
+
259
253
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fs","csvFilesByLanguage: Record<LanguageName, CsvFile>","path","translations: TranslationsByLanguage","defaultValues: TranslationFileContents","phraseTranslations: TranslationsByLanguage","deleteUnusedKeys","phraseDeleteUnusedKeys"],"sources":["../src/file.ts","../src/logger.ts","../src/csv.ts","../src/phrase-api.ts","../src/pull-translations.ts","../src/push-translations.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nexport const mkdir = fs.mkdir;\nexport const writeFile = fs.writeFile;\n","import pc from 'picocolors';\nimport debug from 'debug';\n\nexport const trace = debug(`vocab:phrase`);\n\nexport const log = (...params: unknown[]) => {\n // eslint-disable-next-line no-console\n console.log(pc.yellow('Vocab'), ...params);\n};\n","import { stringify } from 'csv-stringify/sync';\nimport type { LanguageName, TranslationsByLanguage } from '@vocab/core';\n\ntype Value = string | undefined;\ntype CsvRow = Value[];\ntype CsvFile = CsvRow[];\n\nexport function translationsToCsv(\n translations: TranslationsByLanguage,\n devLanguage: string,\n) {\n const languages = Object.keys(translations);\n const altLanguages = languages.filter((language) => language !== devLanguage);\n\n const devLanguageTranslations = translations[devLanguage];\n\n const csvFilesByLanguage: Record<LanguageName, CsvFile> = Object.fromEntries(\n languages.map((language) => [language, []]),\n );\n\n Object.entries(devLanguageTranslations).map(\n ([key, { message, description, tags }]) => {\n const sharedData = [key, description, tags?.join(',')];\n const devLanguageRow = [...sharedData, message];\n csvFilesByLanguage[devLanguage].push(devLanguageRow);\n\n altLanguages.map((language) => {\n const altTranslationMessage = translations[language]?.[key]?.message;\n\n if (altTranslationMessage) {\n csvFilesByLanguage[language].push([\n ...sharedData,\n altTranslationMessage,\n ]);\n }\n });\n },\n );\n\n const csvFileStrings = Object.fromEntries(\n Object.entries(csvFilesByLanguage)\n // Ensure CSV files are only created if the language has at least 1 translation\n .filter(([_, csvFile]) => csvFile.length > 0)\n .map(([language, csvFile]) => {\n const csvFileString = stringify(csvFile, {\n delimiter: ',',\n header: false,\n });\n\n return [language, csvFileString];\n }),\n );\n\n // Column indices start at 1\n const keyIndex = 1;\n const commentIndex = keyIndex + 1;\n const tagColumn = commentIndex + 1;\n const messageIndex = tagColumn + 1;\n\n return { csvFileStrings, keyIndex, messageIndex, commentIndex, tagColumn };\n}\n","/* eslint-disable no-console */\nimport type { TranslationsByLanguage } from '@vocab/core';\nimport { log, trace } from './logger';\nimport { translationsToCsv } from './csv';\n\nfunction _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {\n const phraseApiToken = process.env.PHRASE_API_TOKEN;\n\n if (!phraseApiToken) {\n throw new Error('Missing PHRASE_API_TOKEN');\n }\n\n return fetch(path, {\n ...options,\n headers: {\n Authorization: `token ${phraseApiToken}`,\n // Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent\n 'User-Agent': 'Vocab Client (https://github.com/seek-oss/vocab)',\n ...options.headers,\n },\n }).then(async (response) => {\n console.log(`${path}: ${response.status} - ${response.statusText}`);\n\n const secondsUntilLimitReset = Math.ceil(\n Number.parseFloat(response.headers.get('X-Rate-Limit-Reset') || '0') -\n Date.now() / 1000,\n );\n console.log(\n `Rate Limit: ${response.headers.get(\n 'X-Rate-Limit-Remaining',\n )} of ${response.headers.get(\n 'X-Rate-Limit-Limit',\n )} remaining. (${secondsUntilLimitReset} seconds remaining)`,\n );\n\n trace('\\nLink:', response.headers.get('Link'), '\\n');\n // Print All Headers:\n // console.log(Array.from(r.headers.entries()));\n\n try {\n const result = await response.json();\n\n trace(`Internal Result (Length: ${result.length})\\n`);\n\n if (\n (!options.method || options.method === 'GET') &&\n response.headers.get('Link')?.includes('rel=next')\n ) {\n const [, nextPageUrl] =\n response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];\n\n if (!nextPageUrl) {\n throw new Error(\"Can't parse next page URL\");\n }\n\n console.log('Results received with next page: ', nextPageUrl);\n\n const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;\n\n return [...result, ...nextPageResult];\n }\n\n return result;\n } catch (e) {\n console.error('Unable to parse response as JSON', e);\n return response.text();\n }\n });\n}\n\nexport async function callPhrase<T = any>(\n relativePath: string,\n options: Parameters<typeof fetch>[1] = {},\n): Promise<T> {\n const projectId = process.env.PHRASE_PROJECT_ID;\n\n if (!projectId) {\n throw new Error('Missing PHRASE_PROJECT_ID');\n }\n return _callPhrase(\n `https://api.phrase.com/v2/projects/${projectId}/${relativePath}`,\n options,\n )\n .then((result) => {\n if (Array.isArray(result)) {\n console.log('Result length:', result.length);\n }\n return result;\n })\n .catch((error) => {\n console.error(`Error calling phrase for ${relativePath}:`, error);\n throw Error;\n });\n}\n\nexport async function pullAllTranslations(\n branch: string,\n): Promise<TranslationsByLanguage> {\n const phraseResult = await callPhrase<\n Array<{\n key: { name: string };\n locale: { name: string };\n content: string;\n }>\n >(`translations?branch=${branch}&per_page=100`);\n\n const translations: TranslationsByLanguage = {};\n\n for (const r of phraseResult) {\n if (!translations[r.locale.name]) {\n translations[r.locale.name] = {};\n }\n translations[r.locale.name][r.key.name] = { message: r.content };\n }\n\n return translations;\n}\n\nexport async function pushTranslations(\n translationsByLanguage: TranslationsByLanguage,\n {\n autoTranslate,\n branch,\n devLanguage,\n }: { autoTranslate?: boolean; branch: string; devLanguage: string },\n) {\n const { csvFileStrings, keyIndex, commentIndex, tagColumn, messageIndex } =\n translationsToCsv(translationsByLanguage, devLanguage);\n\n let devLanguageUploadId = '';\n\n for (const [language, csvFileString] of Object.entries(csvFileStrings)) {\n const formData = new FormData();\n\n formData.append(\n 'file',\n new Blob([csvFileString], {\n type: 'text/csv',\n }),\n `${language}.translations.csv`,\n );\n\n formData.append('file_format', 'csv');\n formData.append('branch', branch);\n formData.append('update_translations', 'true');\n formData.append('update_descriptions', 'true');\n\n if (autoTranslate) {\n formData.append('autotranslate', 'true');\n }\n\n formData.append(`locale_mapping[${language}]`, messageIndex.toString());\n\n formData.append('format_options[key_index]', keyIndex.toString());\n formData.append('format_options[comment_index]', commentIndex.toString());\n formData.append('format_options[tag_column]', tagColumn.toString());\n formData.append('format_options[enable_pluralization]', 'false');\n\n log(`Uploading translations for language ${language}`);\n\n const result = await callPhrase<\n | {\n id: string;\n }\n | {\n message: string;\n errors: unknown[];\n }\n | undefined\n >(`uploads`, {\n method: 'POST',\n body: formData,\n });\n\n trace('Upload result:\\n', result);\n\n if (result && 'id' in result) {\n log('Upload ID:', result.id, '\\n');\n log('Successfully Uploaded\\n');\n } else {\n log(`Error uploading: ${result?.message}\\n`);\n log('Response:', result);\n throw new Error('Error uploading');\n }\n\n if (language === devLanguage) {\n devLanguageUploadId = result.id;\n }\n }\n\n return {\n devLanguageUploadId,\n };\n}\n\nexport async function deleteUnusedKeys(uploadId: string, branch: string) {\n const query = `unmentioned_in_upload:${uploadId}`;\n const { records_affected } = await callPhrase<{ records_affected: number }>(\n 'keys',\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ branch, q: query }),\n },\n );\n\n log(\n 'Successfully deleted',\n records_affected,\n 'unused keys from branch',\n branch,\n );\n}\n\nexport async function ensureBranch(branch: string) {\n await callPhrase(`branches`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: branch }),\n });\n\n log('Created branch:', branch);\n}\n","import { writeFile, mkdir } from './file';\nimport path from 'path';\n\nimport {\n type TranslationFileContents,\n type UserConfig,\n loadAllTranslations,\n getAltLanguageFilePath,\n getAltLanguages,\n getUniqueKey,\n} from '@vocab/core';\n\nimport { pullAllTranslations, ensureBranch } from './phrase-api';\nimport { trace } from './logger';\n\ninterface PullOptions {\n branch?: string;\n deleteUnusedKeys?: boolean;\n errorOnNoGlobalKeyTranslation?: boolean;\n}\n\nexport async function pull(\n { branch = 'local-development', errorOnNoGlobalKeyTranslation }: PullOptions,\n config: UserConfig,\n) {\n trace(`Pulling translations from branch ${branch}`);\n await ensureBranch(branch);\n const alternativeLanguages = getAltLanguages(config);\n const allPhraseTranslations = await pullAllTranslations(branch);\n trace(\n `Pulling translations from Phrase for languages ${\n config.devLanguage\n } and ${alternativeLanguages.join(', ')}`,\n );\n\n const phraseLanguages = Object.keys(allPhraseTranslations);\n trace(\n `Found Phrase translations for languages ${phraseLanguages.join(', ')}`,\n );\n\n if (!phraseLanguages.includes(config.devLanguage)) {\n throw new Error(\n `Phrase did not return any translations for the configured development language \"${config.devLanguage}\".\\nPlease ensure this language is present in your Phrase project's configuration.`,\n );\n }\n\n const allVocabTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n config,\n );\n\n for (const loadedTranslation of allVocabTranslations) {\n const devTranslations = loadedTranslation.languages[config.devLanguage];\n\n if (!devTranslations) {\n throw new Error('No dev language translations loaded');\n }\n\n const defaultValues: TranslationFileContents = { ...devTranslations };\n const localKeys = Object.keys(defaultValues);\n\n for (const key of localKeys) {\n defaultValues[key] = {\n ...defaultValues[key],\n ...allPhraseTranslations[config.devLanguage][\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace)\n ],\n };\n }\n\n // Only write a `_meta` field if necessary\n if (Object.keys(loadedTranslation.metadata).length > 0) {\n defaultValues._meta = loadedTranslation.metadata;\n }\n\n await writeFile(\n loadedTranslation.filePath,\n `${JSON.stringify(defaultValues, null, 2)}\\n`,\n );\n\n for (const alternativeLanguage of alternativeLanguages) {\n if (alternativeLanguage in allPhraseTranslations) {\n const altTranslations = {\n ...loadedTranslation.languages[alternativeLanguage],\n };\n const phraseAltTranslations =\n allPhraseTranslations[alternativeLanguage];\n\n for (const key of localKeys) {\n const phraseKey =\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace);\n const phraseTranslationMessage =\n phraseAltTranslations[phraseKey]?.message;\n\n if (!phraseTranslationMessage) {\n trace(\n `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,\n );\n if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {\n throw new Error(\n `Missing translation for global key ${key} in language ${alternativeLanguage}`,\n );\n }\n continue;\n }\n\n altTranslations[key] = {\n ...altTranslations[key],\n message: phraseTranslationMessage,\n };\n }\n\n const altTranslationFilePath = getAltLanguageFilePath(\n loadedTranslation.filePath,\n alternativeLanguage,\n );\n\n await mkdir(path.dirname(altTranslationFilePath), {\n recursive: true,\n });\n await writeFile(\n altTranslationFilePath,\n `${JSON.stringify(altTranslations, null, 2)}\\n`,\n );\n }\n }\n }\n}\n","import {\n loadAllTranslations,\n getUniqueKey,\n type TranslationData,\n type TranslationsByLanguage,\n type UserConfig,\n} from '@vocab/core';\nimport {\n ensureBranch,\n deleteUnusedKeys as phraseDeleteUnusedKeys,\n pushTranslations,\n} from './phrase-api';\nimport { trace } from './logger';\n\ninterface PushOptions {\n autoTranslate?: boolean;\n branch: string;\n deleteUnusedKeys?: boolean;\n ignore?: string[];\n}\n\n/**\n * Uploads translations to the Phrase API for each language.\n * A unique namespace is appended to each key using the file path the key came from.\n */\nexport async function push(\n { autoTranslate, branch, deleteUnusedKeys, ignore }: PushOptions,\n config: UserConfig,\n) {\n if (ignore) {\n trace(`ignoring files on paths: ${ignore.join(', ')}`);\n }\n const allLanguageTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n {\n ...config,\n ignore: [...(config.ignore || []), ...(ignore || [])],\n },\n );\n trace(`Pushing translations to branch ${branch}`);\n const allLanguages = config.languages.map((v) => v.name);\n await ensureBranch(branch);\n\n trace(\n `Pushing translations to phrase for languages ${allLanguages.join(', ')}`,\n );\n\n const phraseTranslations: TranslationsByLanguage = {};\n\n for (const loadedTranslation of allLanguageTranslations) {\n for (const language of allLanguages) {\n const localTranslations = loadedTranslation.languages[language];\n if (!localTranslations) {\n continue;\n }\n if (!phraseTranslations[language]) {\n phraseTranslations[language] = {};\n }\n\n const {\n metadata: { tags: sharedTags = [] },\n } = loadedTranslation;\n\n for (const localKey of Object.keys(localTranslations)) {\n const { tags = [], ...localTranslation } = localTranslations[localKey];\n if (language === config.devLanguage) {\n (localTranslation as TranslationData).tags = [...tags, ...sharedTags];\n }\n const globalKey =\n loadedTranslation.languages[config.devLanguage][localKey].globalKey;\n\n const phraseKey =\n globalKey ?? getUniqueKey(localKey, loadedTranslation.namespace);\n\n phraseTranslations[language][phraseKey] = localTranslation;\n }\n }\n }\n\n const { devLanguageUploadId } = await pushTranslations(phraseTranslations, {\n autoTranslate,\n branch,\n devLanguage: config.devLanguage,\n });\n\n if (deleteUnusedKeys) {\n await phraseDeleteUnusedKeys(devLanguageUploadId, branch);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["fs","deleteUnusedKeys","phraseDeleteUnusedKeys"],"sources":["../src/file.ts","../src/logger.ts","../src/csv.ts","../src/phrase-api.ts","../src/pull-translations.ts","../src/push-translations.ts"],"sourcesContent":["import { promises as fs } from 'fs';\n\nexport const mkdir = fs.mkdir;\nexport const writeFile = fs.writeFile;\n","import pc from 'picocolors';\nimport debug from 'debug';\n\nexport const trace = debug(`vocab:phrase`);\n\nexport const log = (...params: unknown[]) => {\n // eslint-disable-next-line no-console\n console.log(pc.yellow('Vocab'), ...params);\n};\n","import { stringify } from 'csv-stringify/sync';\nimport type { LanguageName, TranslationsByLanguage } from '@vocab/core';\n\ntype Value = string | undefined;\ntype CsvRow = Value[];\ntype CsvFile = CsvRow[];\n\nexport function translationsToCsv(\n translations: TranslationsByLanguage,\n devLanguage: string,\n) {\n const languages = Object.keys(translations);\n const altLanguages = languages.filter((language) => language !== devLanguage);\n\n const devLanguageTranslations = translations[devLanguage];\n\n const csvFilesByLanguage: Record<LanguageName, CsvFile> = Object.fromEntries(\n languages.map((language) => [language, []]),\n );\n\n Object.entries(devLanguageTranslations).map(\n ([key, { message, description, tags }]) => {\n const sharedData = [key, description, tags?.join(',')];\n const devLanguageRow = [...sharedData, message];\n csvFilesByLanguage[devLanguage].push(devLanguageRow);\n\n altLanguages.map((language) => {\n const altTranslationMessage = translations[language]?.[key]?.message;\n\n if (altTranslationMessage) {\n csvFilesByLanguage[language].push([\n ...sharedData,\n altTranslationMessage,\n ]);\n }\n });\n },\n );\n\n const csvFileStrings = Object.fromEntries(\n Object.entries(csvFilesByLanguage)\n // Ensure CSV files are only created if the language has at least 1 translation\n .filter(([_, csvFile]) => csvFile.length > 0)\n .map(([language, csvFile]) => {\n const csvFileString = stringify(csvFile, {\n delimiter: ',',\n header: false,\n });\n\n return [language, csvFileString];\n }),\n );\n\n // Column indices start at 1\n const keyIndex = 1;\n const commentIndex = keyIndex + 1;\n const tagColumn = commentIndex + 1;\n const messageIndex = tagColumn + 1;\n\n return { csvFileStrings, keyIndex, messageIndex, commentIndex, tagColumn };\n}\n","/* eslint-disable no-console */\nimport type { TranslationsByLanguage } from '@vocab/core';\nimport { log, trace } from './logger';\nimport { translationsToCsv } from './csv';\n\nfunction _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {\n const phraseApiToken = process.env.PHRASE_API_TOKEN;\n\n if (!phraseApiToken) {\n throw new Error('Missing PHRASE_API_TOKEN');\n }\n\n return fetch(path, {\n ...options,\n headers: {\n Authorization: `token ${phraseApiToken}`,\n // Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent\n 'User-Agent': 'Vocab Client (https://github.com/seek-oss/vocab)',\n ...options.headers,\n },\n }).then(async (response) => {\n console.log(`${path}: ${response.status} - ${response.statusText}`);\n\n const secondsUntilLimitReset = Math.ceil(\n Number.parseFloat(response.headers.get('X-Rate-Limit-Reset') || '0') -\n Date.now() / 1000,\n );\n console.log(\n `Rate Limit: ${response.headers.get(\n 'X-Rate-Limit-Remaining',\n )} of ${response.headers.get(\n 'X-Rate-Limit-Limit',\n )} remaining. (${secondsUntilLimitReset} seconds remaining)`,\n );\n\n trace('\\nLink:', response.headers.get('Link'), '\\n');\n // Print All Headers:\n // console.log(Array.from(r.headers.entries()));\n\n try {\n const result = await response.json();\n\n trace(`Internal Result (Length: ${result.length})\\n`);\n\n if (\n (!options.method || options.method === 'GET') &&\n response.headers.get('Link')?.includes('rel=next')\n ) {\n const [, nextPageUrl] =\n response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];\n\n if (!nextPageUrl) {\n throw new Error(\"Can't parse next page URL\");\n }\n\n console.log('Results received with next page: ', nextPageUrl);\n\n const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;\n\n return [...result, ...nextPageResult];\n }\n\n return result;\n } catch (e) {\n console.error('Unable to parse response as JSON', e);\n return response.text();\n }\n });\n}\n\nexport async function callPhrase<T = any>(\n relativePath: string,\n options: Parameters<typeof fetch>[1] = {},\n): Promise<T> {\n const projectId = process.env.PHRASE_PROJECT_ID;\n\n if (!projectId) {\n throw new Error('Missing PHRASE_PROJECT_ID');\n }\n return _callPhrase(\n `https://api.phrase.com/v2/projects/${projectId}/${relativePath}`,\n options,\n )\n .then((result) => {\n if (Array.isArray(result)) {\n console.log('Result length:', result.length);\n }\n return result;\n })\n .catch((error) => {\n console.error(`Error calling phrase for ${relativePath}:`, error);\n throw Error;\n });\n}\n\nexport async function pullAllTranslations(\n branch: string,\n): Promise<TranslationsByLanguage> {\n const phraseResult = await callPhrase<\n Array<{\n key: { name: string };\n locale: { name: string };\n content: string;\n }>\n >(`translations?branch=${branch}&per_page=100`);\n\n const translations: TranslationsByLanguage = {};\n\n for (const r of phraseResult) {\n if (!translations[r.locale.name]) {\n translations[r.locale.name] = {};\n }\n translations[r.locale.name][r.key.name] = { message: r.content };\n }\n\n return translations;\n}\n\nexport async function pushTranslations(\n translationsByLanguage: TranslationsByLanguage,\n {\n autoTranslate,\n branch,\n devLanguage,\n }: { autoTranslate?: boolean; branch: string; devLanguage: string },\n) {\n const { csvFileStrings, keyIndex, commentIndex, tagColumn, messageIndex } =\n translationsToCsv(translationsByLanguage, devLanguage);\n\n let devLanguageUploadId = '';\n\n for (const [language, csvFileString] of Object.entries(csvFileStrings)) {\n const formData = new FormData();\n\n formData.append(\n 'file',\n new Blob([csvFileString], {\n type: 'text/csv',\n }),\n `${language}.translations.csv`,\n );\n\n formData.append('file_format', 'csv');\n formData.append('branch', branch);\n formData.append('update_translations', 'true');\n formData.append('update_descriptions', 'true');\n\n if (autoTranslate) {\n formData.append('autotranslate', 'true');\n }\n\n formData.append(`locale_mapping[${language}]`, messageIndex.toString());\n\n formData.append('format_options[key_index]', keyIndex.toString());\n formData.append('format_options[comment_index]', commentIndex.toString());\n formData.append('format_options[tag_column]', tagColumn.toString());\n formData.append('format_options[enable_pluralization]', 'false');\n\n log(`Uploading translations for language ${language}`);\n\n const result = await callPhrase<\n | {\n id: string;\n }\n | {\n message: string;\n errors: unknown[];\n }\n | undefined\n >(`uploads`, {\n method: 'POST',\n body: formData,\n });\n\n trace('Upload result:\\n', result);\n\n if (result && 'id' in result) {\n log('Upload ID:', result.id, '\\n');\n log('Successfully Uploaded\\n');\n } else {\n log(`Error uploading: ${result?.message}\\n`);\n log('Response:', result);\n throw new Error('Error uploading');\n }\n\n if (language === devLanguage) {\n devLanguageUploadId = result.id;\n }\n }\n\n return {\n devLanguageUploadId,\n };\n}\n\nexport async function deleteUnusedKeys(uploadId: string, branch: string) {\n const query = `unmentioned_in_upload:${uploadId}`;\n const { records_affected } = await callPhrase<{ records_affected: number }>(\n 'keys',\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ branch, q: query }),\n },\n );\n\n log(\n 'Successfully deleted',\n records_affected,\n 'unused keys from branch',\n branch,\n );\n}\n\nexport async function ensureBranch(branch: string) {\n await callPhrase(`branches`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: branch }),\n });\n\n log('Created branch:', branch);\n}\n","import { writeFile, mkdir } from './file';\nimport path from 'path';\n\nimport {\n type TranslationFileContents,\n type UserConfig,\n loadAllTranslations,\n getAltLanguageFilePath,\n getAltLanguages,\n getUniqueKey,\n} from '@vocab/core';\n\nimport { pullAllTranslations, ensureBranch } from './phrase-api';\nimport { trace } from './logger';\n\ninterface PullOptions {\n branch?: string;\n deleteUnusedKeys?: boolean;\n errorOnNoGlobalKeyTranslation?: boolean;\n}\n\nexport async function pull(\n { branch = 'local-development', errorOnNoGlobalKeyTranslation }: PullOptions,\n config: UserConfig,\n) {\n trace(`Pulling translations from branch ${branch}`);\n await ensureBranch(branch);\n const alternativeLanguages = getAltLanguages(config);\n const allPhraseTranslations = await pullAllTranslations(branch);\n trace(\n `Pulling translations from Phrase for languages ${\n config.devLanguage\n } and ${alternativeLanguages.join(', ')}`,\n );\n\n const phraseLanguages = Object.keys(allPhraseTranslations);\n trace(\n `Found Phrase translations for languages ${phraseLanguages.join(', ')}`,\n );\n\n if (!phraseLanguages.includes(config.devLanguage)) {\n throw new Error(\n `Phrase did not return any translations for the configured development language \"${config.devLanguage}\".\\nPlease ensure this language is present in your Phrase project's configuration.`,\n );\n }\n\n const allVocabTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n config,\n );\n\n for (const loadedTranslation of allVocabTranslations) {\n const devTranslations = loadedTranslation.languages[config.devLanguage];\n\n if (!devTranslations) {\n throw new Error('No dev language translations loaded');\n }\n\n const defaultValues: TranslationFileContents = { ...devTranslations };\n const localKeys = Object.keys(defaultValues);\n\n for (const key of localKeys) {\n defaultValues[key] = {\n ...defaultValues[key],\n ...allPhraseTranslations[config.devLanguage][\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace)\n ],\n };\n }\n\n // Only write a `_meta` field if necessary\n if (Object.keys(loadedTranslation.metadata).length > 0) {\n defaultValues._meta = loadedTranslation.metadata;\n }\n\n await writeFile(\n loadedTranslation.filePath,\n `${JSON.stringify(defaultValues, null, 2)}\\n`,\n );\n\n for (const alternativeLanguage of alternativeLanguages) {\n if (alternativeLanguage in allPhraseTranslations) {\n const altTranslations = {\n ...loadedTranslation.languages[alternativeLanguage],\n };\n const phraseAltTranslations =\n allPhraseTranslations[alternativeLanguage];\n\n for (const key of localKeys) {\n const phraseKey =\n defaultValues[key].globalKey ??\n getUniqueKey(key, loadedTranslation.namespace);\n const phraseTranslationMessage =\n phraseAltTranslations[phraseKey]?.message;\n\n if (!phraseTranslationMessage) {\n trace(\n `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,\n );\n if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {\n throw new Error(\n `Missing translation for global key ${key} in language ${alternativeLanguage}`,\n );\n }\n continue;\n }\n\n altTranslations[key] = {\n ...altTranslations[key],\n message: phraseTranslationMessage,\n };\n }\n\n const altTranslationFilePath = getAltLanguageFilePath(\n loadedTranslation.filePath,\n alternativeLanguage,\n );\n\n await mkdir(path.dirname(altTranslationFilePath), {\n recursive: true,\n });\n await writeFile(\n altTranslationFilePath,\n `${JSON.stringify(altTranslations, null, 2)}\\n`,\n );\n }\n }\n }\n}\n","import {\n loadAllTranslations,\n getUniqueKey,\n type TranslationData,\n type TranslationsByLanguage,\n type UserConfig,\n} from '@vocab/core';\nimport {\n ensureBranch,\n deleteUnusedKeys as phraseDeleteUnusedKeys,\n pushTranslations,\n} from './phrase-api';\nimport { trace } from './logger';\n\ninterface PushOptions {\n autoTranslate?: boolean;\n branch: string;\n deleteUnusedKeys?: boolean;\n ignore?: string[];\n}\n\n/**\n * Uploads translations to the Phrase API for each language.\n * A unique namespace is appended to each key using the file path the key came from.\n */\nexport async function push(\n { autoTranslate, branch, deleteUnusedKeys, ignore }: PushOptions,\n config: UserConfig,\n) {\n if (ignore) {\n trace(`ignoring files on paths: ${ignore.join(', ')}`);\n }\n const allLanguageTranslations = await loadAllTranslations(\n { fallbacks: 'none', includeNodeModules: false, withTags: true },\n {\n ...config,\n ignore: [...(config.ignore || []), ...(ignore || [])],\n },\n );\n trace(`Pushing translations to branch ${branch}`);\n const allLanguages = config.languages.map((v) => v.name);\n await ensureBranch(branch);\n\n trace(\n `Pushing translations to phrase for languages ${allLanguages.join(', ')}`,\n );\n\n const phraseTranslations: TranslationsByLanguage = {};\n\n for (const loadedTranslation of allLanguageTranslations) {\n for (const language of allLanguages) {\n const localTranslations = loadedTranslation.languages[language];\n if (!localTranslations) {\n continue;\n }\n if (!phraseTranslations[language]) {\n phraseTranslations[language] = {};\n }\n\n const {\n metadata: { tags: sharedTags = [] },\n } = loadedTranslation;\n\n for (const localKey of Object.keys(localTranslations)) {\n const { tags = [], ...localTranslation } = localTranslations[localKey];\n if (language === config.devLanguage) {\n (localTranslation as TranslationData).tags = [...tags, ...sharedTags];\n }\n const globalKey =\n loadedTranslation.languages[config.devLanguage][localKey].globalKey;\n\n const phraseKey =\n globalKey ?? getUniqueKey(localKey, loadedTranslation.namespace);\n\n phraseTranslations[language][phraseKey] = localTranslation;\n }\n }\n }\n\n const { devLanguageUploadId } = await pushTranslations(phraseTranslations, {\n autoTranslate,\n branch,\n devLanguage: config.devLanguage,\n });\n\n if (deleteUnusedKeys) {\n await phraseDeleteUnusedKeys(devLanguageUploadId, branch);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vocab/phrase",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/seek-oss/vocab.git",
@@ -11,8 +11,8 @@
11
11
  "types": "./dist/index.d.cts",
12
12
  "exports": {
13
13
  ".": {
14
- "require": "./dist/index.cjs",
15
- "import": "./dist/index.mjs"
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.cjs"
16
16
  },
17
17
  "./package.json": "./package.json"
18
18
  },
@@ -22,7 +22,7 @@
22
22
  "csv-stringify": "^6.2.3",
23
23
  "debug": "^4.3.1",
24
24
  "picocolors": "^1.0.0",
25
- "@vocab/core": "^1.7.6"
25
+ "@vocab/core": "^1.8.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/debug": "^4.1.5",