scratch-l10n/lib/transifex.js
2024-10-28 14:18:28 -07:00

195 lines
6.1 KiB
JavaScript

#!/usr/bin/env node
/**
* @fileoverview
* Utilities for interfacing with Transifex API 3.
*/
const transifexApi = require('@transifex/api').transifexApi;
const download = require('download');
const ORG_NAME = 'llk';
const SOURCE_LOCALE = 'en';
try {
transifexApi.setup({
auth: process.env.TX_TOKEN
});
} catch (err) {
if (!process.env.TX_TOKEN) {
throw new Error('TX_TOKEN is not defined.');
}
throw err;
}
/**
* Creates a download event for a specific project, resource, and locale.
* @param {string} projectSlug - project slug (for example, "scratch-editor")
* @param {string} resourceSlug - resource slug (for example, "blocks")
* @param {string} localeCode - language code (for example, "ko")
* @param {string} mode - translation status of strings to include
* @returns {Promise<string>} - id of the created download event
*/
const downloadResource = async function (projectSlug, resourceSlug, localeCode, mode = 'default') {
const resource = {
data: {
id: `o:${ORG_NAME}:p:${projectSlug}:r:${resourceSlug}`,
type: 'resources'
}
};
// if locale is English, create a download event of the source file
if (localeCode === SOURCE_LOCALE) {
return await transifexApi.ResourceStringsAsyncDownload.download({
resource
});
}
const language = {
data: {
id: `l:${localeCode}`,
type: 'languages'
}
};
// if locale is not English, create a download event of the translation file
return await transifexApi.ResourceTranslationsAsyncDownload.download({
mode,
resource,
language
});
};
/**
* Pulls a translation json from transifex, for a specific project, resource, and locale.
* @param {string} project - project slug (for example, "scratch-editor")
* @param {string} resource - resource slug (for example, "blocks")
* @param {string} locale - language code (for example, "ko")
* @param {string} mode - translation status of strings to include
* @returns {Promise<object>} - JSON object of translated resource strings (or, of the original resource
* strings, if the local is the source language)
*/
const txPull = async function (project, resource, locale, mode = 'default') {
const url = await downloadResource(project, resource, locale, mode);
let buffer;
for (let i = 0; i < 5; i++) {
try {
buffer = await download(url);
buffer = buffer.toString();
return JSON.parse(buffer);
} catch (e) {
console.error(e, {project, resource, locale, buffer});
console.log(`Retrying after ${i + 1} failed attempt(s)`);
}
}
throw Error('failed to pull after 5 retries');
};
/**
* Given a project, returns a list of the slugs of all resources in the project
* @param {string} project - project slug (for example, "scratch-website")
* @returns {Promise<array>} - array of strings, slugs identifying each resource in the project
*/
const txResources = async function (project) {
const resources = await transifexApi.Resource.filter({
project: `o:${ORG_NAME}:p:${project}`
});
await resources.fetch();
const slugs = resources.data.map(r =>
// r.id is a longer id string, like "o:llk:p:scratch-website:r:about-l10njson"
// We just want the slug that comes after ":r:" ("about-l10njson")
r.id.split(':r:')[1]
);
return slugs;
};
/**
* @param {string} project - project slug (for example)
* @returns {object[]} - array of resource objects
*/
const txResourcesObjects = async function (project) {
const resources = await transifexApi.Resource.filter({
project: `o:${ORG_NAME}:p:${project}`
});
await resources.fetch();
return resources.data;
};
/**
* Gets available languages for a project
* @param {string} slug - project slug (for example, "scratch-editor")
* @returns {Promise<string[]>} - list of language codes
*/
const txAvailableLanguages = async function (slug) {
const project = await transifexApi.Project.get({
organization: `o:${ORG_NAME}`,
slug: slug
});
const languages = await project.fetch('languages');
await languages.fetch();
return languages.data.map(l => l.attributes.code);
};
/**
* Uploads English source strings to a resource in transifex
* @param {string} project - project slug (for example, "scratch-editor")
* @param {string} resource - resource slug (for example, "blocks")
* @param {object} sourceStrings - json of source strings
*/
const txPush = async function (project, resource, sourceStrings) {
const resourceObj = {
data: {
id: `o:${ORG_NAME}:p:${project}:r:${resource}`,
type: 'resources'
}
};
await transifexApi.ResourceStringsAsyncUpload.upload({
resource: resourceObj,
content: JSON.stringify(sourceStrings)
});
};
/**
* Creates a new resource, and then uploads source strings to it if they are provided
* @param {string} project - project slug (for example, "scratch-editor")
* @param {object} resource - object of resource information
* @param {string} resource.slug - resource slug (for example, "blocks")
* @param {string} resource.name - resource name
* @param {string} resource.i18nType - i18n format id
* @param {object} resource.sourceStrings - json object of source strings
*/
const txCreateResource = async function (project, {slug, name, i18nType, sourceStrings}) {
const i18nFormat = {
data: {
id: i18nType || 'KEYVALUEJSON',
type: 'i18n_formats'
}
};
const projectObj = {
data: {
id: `o:${ORG_NAME}:p:${project}`,
type: 'projects'
}
};
await transifexApi.Resource.create({
attributes: {slug: slug, name: name},
relationships: {
i18n_format: i18nFormat,
project: projectObj
}
});
if (sourceStrings) {
await txPush(project, slug, sourceStrings);
}
};
module.exports = {txPull, txPush, txResources, txResourcesObjects, txCreateResource, txAvailableLanguages};