152 lines
		
	
	
	
		
			4.1 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
	
		
			4.1 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Copyright 2021 Signal Messenger, LLC
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
 | 
						|
import { rm, mkdir, writeFile } from 'node:fs/promises';
 | 
						|
import path from 'node:path';
 | 
						|
import fastGlob from 'fast-glob';
 | 
						|
import prettier from 'prettier';
 | 
						|
import pMap from 'p-map';
 | 
						|
import z from 'zod';
 | 
						|
 | 
						|
import { authenticate, API_BASE, PROJECT_ID } from '../util/smartling';
 | 
						|
 | 
						|
const { SMARTLING_USER, SMARTLING_SECRET } = process.env;
 | 
						|
 | 
						|
const RENAMES = new Map([
 | 
						|
  // Smartling uses "zh-YU" for Cantonese (or Yue Chinese).
 | 
						|
  // This is wrong.
 | 
						|
  // The language tag for Yue Chinese is "yue"
 | 
						|
  // "zh-YU" actually implies "Chinese as spoken in Yugoslavia (canonicalized to Serbia)"
 | 
						|
  ['zh-YU', 'yue'],
 | 
						|
 | 
						|
  // For most of the Chinese-speaking world, where we don't have a region specific
 | 
						|
  // locale available (e.g. zh-HK), zh-TW is a suitable choice for "Traditional Chinese".
 | 
						|
  //
 | 
						|
  // However, Intl.LocaleMatcher won't match "zh-Hant-XX" to "zh-TW",
 | 
						|
  // we need to rename it to "zh-Hant" explicitly to make it work.
 | 
						|
  ['zh-TW', 'zh-Hant'],
 | 
						|
 | 
						|
  // "YR" is not a valid region subtag. Smartling made it up.
 | 
						|
  ['sr-YR', 'sr'],
 | 
						|
]);
 | 
						|
 | 
						|
const StatusSchema = z.object({
 | 
						|
  response: z.object({
 | 
						|
    code: z.literal('SUCCESS'),
 | 
						|
    data: z.object({
 | 
						|
      items: z
 | 
						|
        .object({
 | 
						|
          localeId: z.string(),
 | 
						|
        })
 | 
						|
        .array(),
 | 
						|
    }),
 | 
						|
  }),
 | 
						|
});
 | 
						|
 | 
						|
async function main() {
 | 
						|
  if (!SMARTLING_USER) {
 | 
						|
    console.error('Need to set SMARTLING_USER environment variable!');
 | 
						|
    process.exit(1);
 | 
						|
  }
 | 
						|
  if (!SMARTLING_SECRET) {
 | 
						|
    console.error('Need to set SMARTLING_SECRET environment variable!');
 | 
						|
    process.exit(1);
 | 
						|
  }
 | 
						|
 | 
						|
  console.log('Authenticating with Smartling');
 | 
						|
  const headers = await authenticate({
 | 
						|
    userIdentifier: SMARTLING_USER,
 | 
						|
    userSecret: SMARTLING_SECRET,
 | 
						|
  });
 | 
						|
 | 
						|
  const statusURL = new URL(
 | 
						|
    `./files-api/v2/projects/${PROJECT_ID}/file/status`,
 | 
						|
    API_BASE
 | 
						|
  );
 | 
						|
  statusURL.searchParams.set('fileUri', '_locales/en/messages.json');
 | 
						|
 | 
						|
  console.log('Getting list of locales...');
 | 
						|
  const statusRes = await fetch(statusURL, {
 | 
						|
    headers,
 | 
						|
  });
 | 
						|
 | 
						|
  if (!statusRes.ok) {
 | 
						|
    throw new Error('Failed to fetch the status');
 | 
						|
  }
 | 
						|
  if (!statusRes.body) {
 | 
						|
    throw new Error('Missing body');
 | 
						|
  }
 | 
						|
  const {
 | 
						|
    response: {
 | 
						|
      data: { items: locales },
 | 
						|
    },
 | 
						|
  } = StatusSchema.parse(await statusRes.json());
 | 
						|
 | 
						|
  console.log('Cleaning _locales directory...');
 | 
						|
  const dirEntries = await fastGlob(['_locales/*', '!_locales/en'], {
 | 
						|
    onlyDirectories: true,
 | 
						|
    absolute: true,
 | 
						|
  });
 | 
						|
 | 
						|
  await Promise.all(
 | 
						|
    dirEntries.map(dirEntry => rm(dirEntry, { recursive: true }))
 | 
						|
  );
 | 
						|
 | 
						|
  console.log('Getting latest strings');
 | 
						|
 | 
						|
  const prettierConfig = await prettier.resolveConfig('_locales');
 | 
						|
 | 
						|
  await pMap(
 | 
						|
    locales,
 | 
						|
    async ({ localeId }) => {
 | 
						|
      const fileURL = new URL(
 | 
						|
        `./files-api/v2/projects/${PROJECT_ID}/` +
 | 
						|
          `locales/${encodeURIComponent(localeId)}/file`,
 | 
						|
        API_BASE
 | 
						|
      );
 | 
						|
      fileURL.searchParams.set('fileUri', '_locales/en/messages.json');
 | 
						|
      fileURL.searchParams.set('retrievalType', 'published');
 | 
						|
      fileURL.searchParams.set('includeOriginalStrings', 'true');
 | 
						|
 | 
						|
      const fileRes = await fetch(fileURL, {
 | 
						|
        headers,
 | 
						|
      });
 | 
						|
 | 
						|
      if (!fileRes.ok) {
 | 
						|
        throw new Error('Failed to fetch the file');
 | 
						|
      }
 | 
						|
      if (!fileRes.body) {
 | 
						|
        throw new Error('Missing body');
 | 
						|
      }
 | 
						|
 | 
						|
      const targetLocale = RENAMES.get(localeId) ?? localeId;
 | 
						|
      const targetDir = path.join('_locales', targetLocale);
 | 
						|
 | 
						|
      try {
 | 
						|
        await mkdir(targetDir);
 | 
						|
      } catch (error) {
 | 
						|
        console.error(error);
 | 
						|
      }
 | 
						|
 | 
						|
      const targetFile = path.join(targetDir, 'messages.json');
 | 
						|
      console.log('Writing', targetLocale);
 | 
						|
      const json = await fileRes.json();
 | 
						|
      for (const value of Object.values(json)) {
 | 
						|
        const typedValue = value as { description?: string };
 | 
						|
        delete typedValue.description;
 | 
						|
      }
 | 
						|
      delete json.smartling;
 | 
						|
      const output = await prettier.format(JSON.stringify(json, null, 2), {
 | 
						|
        ...prettierConfig,
 | 
						|
        filepath: targetFile,
 | 
						|
      });
 | 
						|
      await writeFile(targetFile, output);
 | 
						|
    },
 | 
						|
    { concurrency: 20 }
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
main().catch(err => {
 | 
						|
  console.error(err);
 | 
						|
  process.exit(1);
 | 
						|
});
 |