/* eslint-env node */ import { default as babelExtractor } from '@lingui/cli/api/extractors/babel' import { createHash } from 'crypto' import { mkdirSync, readFileSync, writeFileSync } from 'fs' import { existsSync } from 'fs' import * as path from 'path' /** A custom caching extractor built on top of babelExtractor. */ const cachingExtractor: typeof babelExtractor = { /** Delegates to babelExtractor.match. */ match(filename: string) { return babelExtractor.match(filename) }, /** * Checks a cache before extraction, only delegating to babelExtractor.extract if the file has changed. * * The lingui extractor works by extracting JSON (the catalog) from `filename` to `buildDir/filename.json`. * Caching works by man-in-the-middling this: * - File freshness is computed as a hash of `filename` contents. * - Before extracting, we check the cache to see if we already have a fresh catalog for the file. * If we do, we copy it to `localeDir/filename.json`. Copying is significantly faster than extracting. * - After extracting, we copy the catalog to the cache. */ extract(filename: string, localeDir: string, ...options: unknown[]) { // This runs from node_modules/@lingui/conf, so we need to back out to the root. const root = __dirname.split('/node_modules')[0] // This logic mimics catalogFilename in @lingui/babel-plugin-extract-messages. const buildDir = path.join(localeDir, '_build') const localePath = path.join(buildDir, filename + '.json') const filePath = path.join(root, filename) const fileHash = createHash('sha256').update(readFileSync(filePath)).digest('hex') const cacheRoot = path.join(root, 'node_modules/.cache/lingui') const cachePath = path.join(cacheRoot, filename + '.json') // If we have a matching cached copy of the catalog, we can copy it to localePath and return early. if (existsSync(cachePath)) { const { hash, catalog } = JSON.parse(readFileSync(cachePath, 'utf8')) if (hash === fileHash) { if (catalog) { mkdirSync(path.dirname(localePath), { recursive: true }) writeFileSync(localePath, JSON.stringify(catalog, null, 2)) } return } } babelExtractor.extract(filename, localeDir, ...options) // Cache the extracted catalog. mkdirSync(path.dirname(cachePath), { recursive: true }) if (existsSync(localePath)) { const catalog = JSON.parse(readFileSync(localePath, 'utf8')) writeFileSync(cachePath, JSON.stringify({ hash: fileHash, catalog })) } else { writeFileSync(cachePath, JSON.stringify({ hash: fileHash })) } }, } const linguiConfig = { catalogs: [ { path: '/src/locales/{locale}', include: ['/src/**/*.ts', '/src/**/*.tsx'], exclude: [ '/src/**/*.d.ts', '/src/**/*.test.*', '/src/types/v3/**', '/src/abis/types/**', '/src/graphql/**/__generated__/**', ], }, ], compileNamespace: 'cjs', fallbackLocales: { default: 'en-US', }, format: 'po', formatOptions: { lineNumbers: false, }, locales: [ 'af-ZA', 'ar-SA', 'ca-ES', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-US', 'es-ES', 'fi-FI', 'fr-FR', 'he-IL', 'hu-HU', 'id-ID', 'it-IT', 'ja-JP', 'ko-KR', 'nl-NL', 'no-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'sr-SP', 'sv-SE', 'sw-TZ', 'tr-TR', 'uk-UA', 'vi-VN', 'zh-CN', 'zh-TW', 'pseudo', ], orderBy: 'messageId', rootDir: '.', runtimeConfigModule: ['@lingui/core', 'i18n'], sourceLocale: 'en-US', pseudoLocale: 'pseudo', extractors: [cachingExtractor], } export default linguiConfig