142 lines
3.5 KiB
JavaScript
142 lines
3.5 KiB
JavaScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import rdir from 'recursive-readdir'
|
|
import yaml from 'js-yaml'
|
|
|
|
/*
|
|
* List of supported languages
|
|
*/
|
|
const locales = ['en', 'es', 'de', 'fr', 'nl']
|
|
|
|
/*
|
|
* This is where we configure what folders we should check for
|
|
* code-adjacent translation source files
|
|
*/
|
|
const folders = [path.resolve(path.join('.', 'src'))]
|
|
|
|
/*
|
|
* Helper method to write out JSON files for translation sources
|
|
*/
|
|
const writeJson = async (locale, namespace, content) =>
|
|
fs.writeFileSync(
|
|
path.resolve('.', 'public', 'locales', locale, `${namespace}.json`),
|
|
JSON.stringify(content)
|
|
)
|
|
|
|
/*
|
|
* Helper method to get a list of code-adjecent translation files in a folder.
|
|
* Will traverse recursively to get all files from a given root folder.
|
|
*/
|
|
const getI18nFileList = async () => {
|
|
const allFiles = []
|
|
for (const dir of folders) {
|
|
try {
|
|
const dirFiles = await rdir(dir)
|
|
allFiles.push(...dirFiles)
|
|
} catch (err) {
|
|
console.log(err)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter out the language files
|
|
return allFiles
|
|
.filter((file) => locales.map((loc) => `.${loc}.yaml`).includes(file.slice(-8)))
|
|
.sort()
|
|
}
|
|
|
|
/*
|
|
* Helper method to get language and namespace from the filename
|
|
*
|
|
* Parameters:
|
|
*
|
|
* - filename: The filename or full path + filename
|
|
*/
|
|
const languageAndNamespaceFromFilename = (file) => {
|
|
const chunks = path.basename(file).split('.')
|
|
chunks.pop()
|
|
|
|
return chunks
|
|
}
|
|
|
|
/*
|
|
* Helper method to load a YAML file from disk
|
|
*/
|
|
const loadYaml = (file) => {
|
|
let data
|
|
try {
|
|
data = yaml.load(fs.readFileSync(file, 'utf-8'))
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
/*
|
|
* Helper method to build an object of namespaces and their values.
|
|
* Includes providing an EN fallback if something is not available in a language.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* - files: List of files to process
|
|
*/
|
|
const filesAsNamespaces = (files) => {
|
|
// First build the object
|
|
const translations = {}
|
|
for (const file of files) {
|
|
const [namespace, lang] = languageAndNamespaceFromFilename(file)
|
|
if (typeof translations[namespace] === 'undefined') {
|
|
translations[namespace] = {}
|
|
}
|
|
translations[namespace][lang] = loadYaml(file)
|
|
}
|
|
|
|
return translations
|
|
}
|
|
|
|
/*
|
|
* Helper method to ensure all translations all available in the data
|
|
*
|
|
* Parameter:
|
|
*
|
|
* - data: The raw data based on loaded YAML files
|
|
*/
|
|
const fixData = (rawData) => {
|
|
const data = {}
|
|
for (const [namespace, nsdata] of Object.entries(rawData)) {
|
|
if (typeof nsdata.en === 'undefined') {
|
|
throw `No English data for namespace ${namespace}. Bailing out`
|
|
}
|
|
data[namespace] = { en: nsdata.en }
|
|
// Complete other locales
|
|
for (const lang of locales.filter((loc) => loc !== 'en')) {
|
|
if (typeof nsdata[lang] === 'undefined') data[namespace][lang] = nsdata.en
|
|
else {
|
|
for (const key of Object.keys(data[namespace].en)) {
|
|
if (typeof nsdata[lang][key] === 'undefined') nsdata[lang][key] = nsdata.en[key]
|
|
}
|
|
data[namespace][lang] = nsdata[lang]
|
|
}
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
/*
|
|
* The method that does the actual work
|
|
*/
|
|
export const prebuildI18n = async () => {
|
|
// Handle new code-adjacent translations
|
|
const files = await getI18nFileList()
|
|
const data = filesAsNamespaces(files)
|
|
const namespaces = fixData(data)
|
|
// Write out code-adjacent source files
|
|
for (const locale of locales) {
|
|
// Fan out into namespaces
|
|
for (const namespace in namespaces) {
|
|
writeJson(locale, namespace, namespaces[namespace][locale])
|
|
}
|
|
}
|
|
}
|