diff --git a/.unimportedrc.json b/.unimportedrc.json index 70017970..941f62eb 100644 --- a/.unimportedrc.json +++ b/.unimportedrc.json @@ -6,7 +6,19 @@ "#assets/content/operational_timeline_title.svg?react", "#assets/content/operational_timeline_body.svg?react" ], - "ignoreUnused": ["@tinymce/tinymce-react", "@mapbox/mapbox-gl-draw"], + "ignoreUnused": [ + "@tinymce/tinymce-react", + "@mapbox/mapbox-gl-draw", + "@apollo/client", + "@graphql-codegen/introspection", + "@graphql-codegen/typescript-operations", + "@togglecorp/re-map", + "@turf/bbox", + "@turf/buffer", + "graphql-request", + "sanitize-html", + "@sentry/react" + ], "extensions": [".ts", ".js", ".tsx", ".jsx"], "aliases": { "#assets/*": ["./src/assets/*"], diff --git a/package.json b/package.json index 013d5758..3ec19710 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@graphql-codegen/introspection": "^4.0.3", "@graphql-codegen/typescript-operations": "^4.2.0", "@ifrc-go/icons": "^1.3.1", + "@sentry/react": "^7.81.1", "@mapbox/mapbox-gl-draw": "^1.2.0", "@togglecorp/fujs": "^2.1.1", "@togglecorp/re-map": "^0.2.0-beta-6", diff --git a/scripts/translator.js b/scripts/translator.js new file mode 100644 index 00000000..3f78c209 --- /dev/null +++ b/scripts/translator.js @@ -0,0 +1,83 @@ +import { isDefined, listToMap, mapToList } from '@togglecorp/fujs'; +import fg from 'fast-glob'; +import { readFile } from 'fs'; +import { join } from 'path'; +import { cwd, exit } from 'process'; +import { promisify } from 'util'; + +const glob = fg.glob; + +const readFilePromisify = promisify(readFile); + +function getDuplicates( + list, + keySelector, +) { + if (!list) { + return undefined; + } + const counts = listToMap( + list, + keySelector, + (_, key, __, acc) => { + const value = acc[key]; + return isDefined(value) ? value + 1 : 1; + }, + ); + + return list + .filter((item) => counts[keySelector(item)] > 1) + .sort((foo, bar) => keySelector(foo).localeCompare(keySelector(bar))); +} + +const currentDir = cwd(); +const fullPath = join(currentDir, 'src/**/i18n.json'); +console.info('Searching in', fullPath); + +const files = await glob(fullPath, { ignore: ['node_modules'], absolute: true }); +console.info(`Found ${files.length} i18n.json files.`); + +const translationsPromise = files.map(async (file) => { + const fileDescriptor = await readFilePromisify(file); + const filename = `.${file.slice(currentDir.length)}`; + try { + return { + file: filename, + content: JSON.parse(fileDescriptor.toString()), + }; + } catch (e) { + console.error(`Error while parsing JSON for ${filename}`); + exit(1); + } +}); +const translations = await Promise.all(translationsPromise); + +const strings = translations.flatMap((translation) => { + const { file, content } = translation; + + return mapToList( + content.strings, + (item, key) => ({ + file, + namespace: content.namespace, + key, + value: item, + }), + ); +}); + +const namespaces = new Set(strings.map((item) => item.namespace)); + +console.info(`Found ${namespaces.size} namespaces.`); +console.info(`Found ${strings.length} strings.`); + +const duplicates = getDuplicates( + strings, + (string) => `${string.namespace}:${string.key}`, +); + +console.error(`Found ${duplicates.length} duplicated strings.`); +if (duplicates.length > 0) { + console.info(JSON.stringify(duplicates, null, 2)); + exit(2); +}