-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fet-1603: Scan unused transaltion keys
- Loading branch information
Stanislav Lysak
committed
Aug 23, 2024
1 parent
bd5e3a8
commit 36087a4
Showing
5 changed files
with
216 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,51 @@ | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
const BASE_LOCALE = 'en' | ||
const BASE_DIR = 'public/locales' | ||
|
||
function listLocales(dirPath, arrayOfFiles) { | ||
arrayOfFiles = arrayOfFiles || [] | ||
|
||
const files = fs.readdirSync(dirPath) | ||
|
||
files.forEach(function (file) { | ||
const fullPath = path.join(dirPath, file) | ||
|
||
if (fs.statSync(fullPath).isDirectory()) { | ||
arrayOfFiles.push(fullPath) | ||
listLocales(fullPath, arrayOfFiles) // Recursively list files in subdirectories | ||
} else { | ||
arrayOfFiles.push(fullPath) | ||
} | ||
}) | ||
|
||
return arrayOfFiles | ||
} | ||
|
||
function detectMissingKeys(jsonObject, template) { | ||
let missingKeys = [] | ||
|
||
for (const key in template) { | ||
if (!jsonObject.hasOwnProperty(key)) { | ||
missingKeys.push(key) | ||
} else if (typeof template[key] === 'object' && !Array.isArray(template[key])) { | ||
const nestedMissingKeys = detectMissingKeys(jsonObject[key], template[key]) | ||
if (nestedMissingKeys.length > 0) { | ||
missingKeys = [ | ||
...missingKeys, | ||
...nestedMissingKeys.map((nestedKey) => `${key}.${nestedKey}`), | ||
] | ||
import { BASE_LOCALE, getLocalePaths, LOCALES_DIR } from './locale-utils.mjs' | ||
|
||
;(() => { | ||
function detectMissingKeys(jsonObject, template) { | ||
let missingKeys = [] | ||
|
||
for (const key in template) { | ||
if (!jsonObject.hasOwnProperty(key)) { | ||
missingKeys.push(key) | ||
} else if (typeof template[key] === 'object' && !Array.isArray(template[key])) { | ||
const nestedMissingKeys = detectMissingKeys(jsonObject[key], template[key]) | ||
if (nestedMissingKeys.length > 0) { | ||
missingKeys = [ | ||
...missingKeys, | ||
...nestedMissingKeys.map((nestedKey) => `${key}.${nestedKey}`), | ||
] | ||
} | ||
} | ||
} | ||
|
||
return missingKeys | ||
} | ||
|
||
return missingKeys | ||
} | ||
const locales = getLocalePaths() | ||
|
||
const locales = fs.readdirSync(BASE_DIR).reduce((result, key) => { | ||
result[key] = listLocales(`${BASE_DIR}/${key}`) | ||
return result | ||
}, {}) | ||
console.log('locales', locales) | ||
|
||
const baseLocale = locales[BASE_LOCALE] | ||
const baseLocale = locales[BASE_LOCALE] | ||
|
||
for (const key in locales) { | ||
if (key !== BASE_LOCALE) { | ||
for (const filePath of baseLocale) { | ||
const diffPath = filePath.replace(`/${BASE_LOCALE}/`, `/${key}/`) | ||
const source = JSON.parse(fs.readFileSync(filePath, 'utf-8')) | ||
const template = JSON.parse( | ||
fs.existsSync(diffPath) ? fs.readFileSync(diffPath, 'utf-8') : '{}', | ||
) | ||
for (const key in locales) { | ||
if (key !== BASE_LOCALE) { | ||
for (const filePath of baseLocale) { | ||
const diffPath = filePath.replace(`/${BASE_LOCALE}/`, `/${key}/`) | ||
const source = JSON.parse(fs.readFileSync(filePath, 'utf-8')) | ||
const template = JSON.parse( | ||
fs.existsSync(diffPath) ? fs.readFileSync(diffPath, 'utf-8') : '{}', | ||
) | ||
|
||
const keys = detectMissingKeys(template, source) | ||
const keys = detectMissingKeys(template, source) | ||
|
||
if (keys.length) { | ||
console.log('\n') | ||
console.log(key, diffPath.replace(BASE_DIR, '')) | ||
console.log(keys.join('\n')) | ||
if (keys.length) { | ||
console.log('\n') | ||
console.log(key, diffPath.replace(LOCALES_DIR, '')) | ||
console.log(keys.join('\n')) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
export const BASE_LOCALE = 'en' | ||
export const LOCALES_DIR = 'public/locales' | ||
|
||
function getDotNotationKeys(obj, parent = '') { | ||
let keys = [] | ||
|
||
for (let key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
const fullKey = parent ? `${parent}.${key}` : key | ||
if (typeof obj[key] === 'object' && obj[key] !== null) { | ||
keys = keys.concat(getDotNotationKeys(obj[key], fullKey)) | ||
} else { | ||
keys.push(fullKey) | ||
} | ||
} | ||
} | ||
|
||
return keys | ||
} | ||
|
||
function listLocales(dirPath, arrayOfFiles) { | ||
arrayOfFiles = arrayOfFiles || [] | ||
|
||
const files = fs.readdirSync(dirPath) | ||
|
||
files.forEach(function (file) { | ||
const fullPath = path.join(dirPath, file) | ||
|
||
if (fs.statSync(fullPath).isDirectory()) { | ||
arrayOfFiles.push(fullPath) | ||
listLocales(fullPath, arrayOfFiles) // Recursively list files in subdirectories | ||
} else { | ||
arrayOfFiles.push(fullPath) | ||
} | ||
}) | ||
|
||
return arrayOfFiles | ||
} | ||
|
||
export function getLocalePaths(locale) { | ||
const locales = fs.readdirSync(LOCALES_DIR).reduce((result, key) => { | ||
result[key] = listLocales(`${LOCALES_DIR}/${key}`) | ||
return result | ||
}, {}) | ||
|
||
if (locale) return locales[locale] | ||
|
||
return locales | ||
} | ||
|
||
export function getLocaleData(filePaths) { | ||
let keys = [] | ||
const namespaces = [] | ||
|
||
for (const filePath of filePaths) { | ||
const content = fs.readFileSync(filePath, 'utf-8') | ||
const json = JSON.parse(content) | ||
|
||
const ns = filePath.split('/').at(-1).replace('.json', '') | ||
|
||
namespaces.push(ns) | ||
keys = [...keys, ...getDotNotationKeys({ [ns]: json })] | ||
} | ||
|
||
return { keys, namespaces } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
import { BASE_LOCALE, getLocaleData, getLocalePaths } from './locale-utils.mjs' | ||
|
||
const baseLocale = getLocalePaths(BASE_LOCALE) | ||
|
||
;(() => { | ||
const { keys, namespaces } = getLocaleData(baseLocale) | ||
|
||
function createRegex(text, { caseInsensitive = true } = {}) { | ||
const escapedPattern = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') | ||
const flags = caseInsensitive ? 'gi' : 'g' | ||
return new RegExp(`\\b${escapedPattern}`, flags) | ||
} | ||
|
||
function filterByExt(text, exts = []) { | ||
const regex = new RegExp(`(${exts.join('|')})$`) | ||
return regex.test(text) | ||
} | ||
|
||
const dir = './src' | ||
const search = `t('` | ||
const regex = createRegex(search) | ||
|
||
const results = { | ||
results: [], | ||
files: [], | ||
} | ||
|
||
function extractKey(str) { | ||
const keyRegex = /t\(\s*['"](.+?)['"]\s*,?/ | ||
const keyMatch = str.match(keyRegex) | ||
return keyMatch ? keyMatch[1] : null | ||
} | ||
|
||
function extractMatch(filePath) { | ||
let match = true | ||
const matches = [] | ||
let content = fs.readFileSync(filePath, 'utf-8') | ||
|
||
while ((match = regex.exec(content))) { | ||
// /\b(?:t)\s*\(\s*(['\s\S']*?)\s*\)/g | ||
const line = /\b(?:t)\s*\(['"][^'"]+['"][^)]*\)/g.exec(content)?.at(0) | ||
content = content.replace(match?.[0], '').replace(line, '') | ||
matches.push(extractKey(line)) | ||
} | ||
|
||
return matches | ||
} | ||
|
||
function handleResults(filePath) { | ||
const matches = extractMatch(filePath) | ||
|
||
if (!matches.length) return | ||
|
||
// console.log(`Found ${matches.length} ${search} in ${filePath}:`) | ||
matches.forEach((m) => console.log(m)) | ||
// console.log('\n') | ||
results.results = [...results.results, ...matches] | ||
results.files = [...results.files, filePath] | ||
} | ||
|
||
// Function to recursively scan files in a directory | ||
function scanFiles({ dir, fn, ext = [] }) { | ||
const files = fs.readdirSync(dir) | ||
|
||
files.forEach((file) => { | ||
const filePath = path.join(dir, file) | ||
const stat = fs.statSync(filePath) | ||
|
||
if (stat.isDirectory()) { | ||
scanFiles({ dir: filePath, fn, ext }) // Recursively scan subdirectories | ||
} else if (stat.isFile() && filterByExt(file, ext)) { | ||
fn(filePath) | ||
} | ||
}) | ||
} | ||
|
||
scanFiles({ | ||
dir, | ||
fn: handleResults, | ||
ext: ['.ts', '.tsx'], | ||
}) | ||
|
||
const unusedKeys = [] | ||
const foundKeys = [ | ||
...new Set( | ||
results.results.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), '')), | ||
), | ||
] | ||
const modifiedKeys = [ | ||
...new Set(keys.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''))), | ||
] | ||
|
||
for (const key of modifiedKeys) { | ||
const foundKey = foundKeys.find((k) => key === k) | ||
|
||
if (!foundKey) { | ||
unusedKeys.push(key) | ||
} | ||
} | ||
|
||
console.log('PROBABLY UNSED KEYS\n') | ||
for (const key of unusedKeys) { | ||
console.log(key) | ||
} | ||
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters