transifex.js 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. const fs = require('fs').promises;
  2. const spawn = require('cross-spawn');
  3. const yaml = require('js-yaml');
  4. function delay(time) {
  5. return new Promise(resolve => setTimeout(resolve, time));
  6. }
  7. function transifexRequest(url, method = 'GET') {
  8. return new Promise((resolve, reject) => {
  9. const child = spawn('curl', [
  10. '-sSL',
  11. '--user',
  12. `api:${process.env.TRANSIFEX_TOKEN}`,
  13. '-X',
  14. method,
  15. `https://www.transifex.com${url}`,
  16. ], { stdio: ['ignore', 'pipe', 'inherit'] });
  17. const stdoutBuffer = [];
  18. child.stdout.on('data', chunk => {
  19. stdoutBuffer.push(chunk);
  20. })
  21. child.on('exit', (code) => {
  22. if (code) {
  23. reject(code);
  24. } else {
  25. const stdout = Buffer.concat(stdoutBuffer).toString('utf8');
  26. try {
  27. resolve(JSON.parse(stdout));
  28. } catch (err) {
  29. console.error('stdout: ' + stdout);
  30. throw err;
  31. }
  32. }
  33. });
  34. });
  35. }
  36. async function getLanguages() {
  37. const result = await transifexRequest('/api/2/project/violentmonkey-nex/?details');
  38. return result.teams;
  39. }
  40. async function loadRemote(lang) {
  41. // Reference: https://docs.transifex.com/api/translations#downloading-and-uploading-translations
  42. // Use translated messages since we don't have enough reviewers
  43. const result = await transifexRequest(`/api/2/project/violentmonkey-nex/resource/messagesjson/translation/${lang}/?mode=onlytranslated`);
  44. const reviewed = JSON.parse(result.content);
  45. return reviewed;
  46. }
  47. async function extract(lang) {
  48. const reviewed = await loadRemote(lang);
  49. const fullPath = `src/_locales/${lang}/messages.yml`;
  50. const existed = yaml.safeLoad(await fs.readFile(fullPath, 'utf8'));
  51. Object.entries(existed)
  52. .forEach(([key, value]) => {
  53. const reviewedMessage = reviewed[key] && reviewed[key].message;
  54. if (reviewedMessage) value.message = reviewedMessage;
  55. });
  56. await fs.writeFile(fullPath, yaml.safeDump(existed), 'utf8');
  57. }
  58. async function main() {
  59. process.stdout.write('Loading languages...');
  60. const codes = await getLanguages();
  61. process.stdout.write('OK\n');
  62. process.stdout.write(`Got ${codes.length} language codes\n`);
  63. for (const code of codes) {
  64. await fs.mkdir(`src/_locales/${code}`, { recursive: true });
  65. }
  66. spawn.sync('yarn', ['i18n'], { stdio: 'inherit' });
  67. let finished = 0;
  68. const showProgress = () => {
  69. process.stdout.write(`\rLoading translations (${finished}/${codes.length})...`);
  70. };
  71. showProgress();
  72. for (const code of codes) {
  73. await delay(500);
  74. try {
  75. await extract(code);
  76. } catch (err) {
  77. process.stderr.write(`\nError pulling ${code}\n`)
  78. }
  79. finished += 1;
  80. showProgress();
  81. }
  82. process.stdout.write('OK\n');
  83. }
  84. main();