267 lines
9.5 KiB
JavaScript
267 lines
9.5 KiB
JavaScript
import path from 'path'
|
||
import fs from 'fs-extra'
|
||
|
||
import extractRelativeTimeMessages from '../source/CLDR/extractRelativeTimeMessages'
|
||
import getLocalesListInCLDR from '../source/CLDR/getLocalesList'
|
||
|
||
// CLDR stubs missing translations with English ones.
|
||
// This can be used to find out whether a translation is missing.
|
||
const LONG_STYLE_TRANSLATION_STUB = `{
|
||
"year": {
|
||
"previous": "last year",
|
||
"current": "this year",
|
||
"next": "next year",
|
||
"past": "-{0} y",
|
||
"future": "+{0} y"
|
||
}`
|
||
|
||
// Generate plurals first, then run this script.
|
||
// Generating plurals creates locale folder structure.
|
||
//
|
||
// ```
|
||
// npm run generate-locale-quantifiers
|
||
// npm run generate-locale-messages
|
||
// // npm run generate-load-all-locales
|
||
// ````
|
||
for (const locale of getLocalesListInCLDR())
|
||
{
|
||
if (
|
||
// Different variations of "en" language have `en/short.json` and `en/narrow.json`
|
||
// which differ from one another by a simple dot, e.g. `yr.` vs `yr`.
|
||
// To reduce the resulting bundle size this dot difference is considered unimportant.
|
||
locale.indexOf('en-') === 0 ||
|
||
// For "pt" language the relative time messages seem to be different
|
||
// from all other "pt-" variations which are identical to "pt-PT".
|
||
// Seems like a bug in CLDR.
|
||
// To reduce the resulting bundle size "pt" language is discarded
|
||
// and "pt-PT" is used instead. All other "pt-" variations
|
||
// seems to be identical to "pt-PT" so they're not included.
|
||
locale.indexOf('pt-') === 0
|
||
) {
|
||
// Delete the locale folder.
|
||
// (previously created by `npm run generate-locale-quantifiers`).
|
||
fs.removeSync(path.join(__dirname, '../locale', locale))
|
||
continue
|
||
}
|
||
|
||
// console.log(locale)
|
||
|
||
// "language" is the top-most parent locale of the `locale`.
|
||
const language = locale.split('-')[0]
|
||
|
||
// For "pt" language the relative time messages seem to be different
|
||
// from all other "pt-" variations which are identical to "pt-PT".
|
||
// Seems like a bug in CLDR.
|
||
// To reduce the resulting bundle size "pt" language is discarded
|
||
// and "pt-PT" is used instead. All other "pt-" variations
|
||
// seems to be identical to "pt-PT" so they're not included.
|
||
const localeInCLDR = locale === 'pt' ? 'pt-PT' : locale
|
||
|
||
const cldrJsonPath = `cldr-dates-full/main/${localeInCLDR}/dateFields.json`
|
||
const localeDirectory = path.join(__dirname, '../locale', locale)
|
||
const languageDirectory = path.join(__dirname, '../locale', language)
|
||
|
||
// If there's no pluralization classifier function
|
||
// for this language then don't add it.
|
||
const quantifyDirectory = findQuantifyDirectory(locale)
|
||
|
||
const localeMessages = extractRelativeTimeMessages(require(cldrJsonPath))
|
||
|
||
// "long" messages are always present.
|
||
if (!localeMessages.long) {
|
||
throw new Error(`Default (long) locale data is missing for locale "${locale}".`)
|
||
}
|
||
|
||
// If there are no translations for a locale then skip it.
|
||
if (JSON.stringify(localeMessages.long, null, '\t').indexOf(LONG_STYLE_TRANSLATION_STUB) === 0) {
|
||
// console.log(`No translation for "${locale}". Skipping.`)
|
||
fs.removeSync(localeDirectory)
|
||
continue
|
||
}
|
||
|
||
// "short" messages are always present.
|
||
if (!localeMessages.short) {
|
||
throw new Error(`Short locale data is missing for locale "${locale}".`)
|
||
}
|
||
|
||
// "narrow" messages are always present.
|
||
if (!localeMessages.narrow) {
|
||
throw new Error(`Narrow locale data is missing for locale "${locale}".`)
|
||
}
|
||
|
||
// Drop duplicate quantifier messages.
|
||
compactQuantifiersData(localeMessages.long)
|
||
compactQuantifiersData(localeMessages.short)
|
||
compactQuantifiersData(localeMessages.narrow)
|
||
|
||
// What are "narrow" and "short" styles and how are they constructed:
|
||
// http://cldr.unicode.org/translation/plurals#TOC-Narrow-and-Short-Forms
|
||
|
||
// Create `index.js` file in the locale directory.
|
||
fs.outputFileSync(
|
||
path.join(localeDirectory, 'index.js'),
|
||
`
|
||
module.exports = {
|
||
${[
|
||
"locale: '" + locale + "'",
|
||
"long: require('" + createTimeLabels(locale, 'long', localeMessages) + "')",
|
||
"short: require('" + createTimeLabels(locale, 'short', localeMessages) + "')",
|
||
"narrow: require('" + createTimeLabels(locale, 'narrow', localeMessages) + "')",
|
||
quantifyDirectory && ("quantify: require('" + quantifyDirectory + "/quantify')")
|
||
]
|
||
.filter(_ => _)
|
||
.join(',\n\t')}
|
||
}
|
||
`.trim()
|
||
)
|
||
|
||
// Remove all locales containing just `index.js`
|
||
// which means they're fully inherting from their parent locale.
|
||
for (const locale of getLocalesListGenerated()) {
|
||
const files = fs.readdirSync(path.join(__dirname, '../locale', locale))
|
||
if (files.length === 1 && files[0] === 'index.js') {
|
||
fs.removeSync(path.resolve(__dirname, '../locale', locale))
|
||
}
|
||
}
|
||
|
||
// Remove strange locales.
|
||
fs.removeSync(path.resolve(__dirname, '../locale/en-001'))
|
||
fs.removeSync(path.resolve(__dirname, '../locale/en-150'))
|
||
fs.removeSync(path.resolve(__dirname, '../locale/en-US-POSIX'))
|
||
fs.removeSync(path.resolve(__dirname, '../locale/es-419'))
|
||
}
|
||
|
||
/**
|
||
* CLDR data always has relative time messages duplicated
|
||
* for all keys if they're the same.
|
||
* For example, if relative time messages are the same for
|
||
* "one", "many" and "other" they will still be duplicated
|
||
* in CLDR data files.
|
||
* This function removes such duplication to reduce the
|
||
* resulting bundle size.
|
||
* Mutates the object passed as the argument directly.
|
||
* @param {object} flavor — Relative time messages of a given flavor.
|
||
*/
|
||
function compactQuantifiersData(flavour) {
|
||
for (const unit of Object.keys(flavour)) {
|
||
for (const pastOrFuture of Object.keys(flavour[unit])) {
|
||
for (const quantifier of Object.keys(flavour[unit][pastOrFuture])) {
|
||
// "other" is the one holding the relative time message in case of duplication.
|
||
if (quantifier === 'other') {
|
||
continue
|
||
}
|
||
if (flavour[unit][pastOrFuture][quantifier] === flavour[unit][pastOrFuture].other) {
|
||
delete flavour[unit][pastOrFuture][quantifier]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns a list of all supported locales.
|
||
* @return {string[]}
|
||
*/
|
||
function getLocalesListGenerated() {
|
||
return fs.readdirSync(path.join(__dirname, '../locale'))
|
||
.filter(_ => fs.statSync(path.join(__dirname, '../locale', _)).isDirectory())
|
||
}
|
||
|
||
/**
|
||
* Creates a file with the time messages
|
||
* of a given style for a given locale.
|
||
* @param {string} locale
|
||
* @param {string} style
|
||
* @param {object} localeMessages — Time messages object containing all styles.
|
||
* @return {object} The relative path to the time messages file.
|
||
*/
|
||
function createTimeLabels(locale, style, localeMessages) {
|
||
const content = JSON.stringify(localeMessages[style], null, '\t')
|
||
// `sr-Cyrl-BA` -> `sr-Cyrl` -> `sr`.
|
||
const parentLocale = findParentLocaleHavingFile(locale, `${style}.json`, {
|
||
condition: (file) => fs.readFileSync(file, 'utf-8') === content
|
||
})
|
||
if (parentLocale) {
|
||
return `../${parentLocale}/${style}.json`
|
||
}
|
||
fs.outputFileSync(path.join(__dirname, '../locale', locale, `${style}.json`), content)
|
||
return `./${style}.json`
|
||
}
|
||
|
||
/**
|
||
* Returns the relative path to a directory where
|
||
* `quantify.js` resides for a given locale.
|
||
* For example, there are different locales: "ar" and "ar-AE".
|
||
* But their `quantify()` function is identical.
|
||
* Therefore, it's only stored inside `ar` folder
|
||
* and `getQuantifyDirectory('ar-AE')` is "../ar".
|
||
* @param {string} locale
|
||
* @return {string} [directory]
|
||
*/
|
||
function findQuantifyDirectory(locale) {
|
||
// Look in parent locales' folders.
|
||
// Example: `sr-Cyrl-BA` -> `sr-Cyrl` -> `sr`.
|
||
const parentLocale = findParentLocaleHavingFile(locale, 'quantify.js')
|
||
if (parentLocale) {
|
||
return `../${parentLocale}`
|
||
}
|
||
// Look in this locale's folder.
|
||
if (fs.existsSync(path.join(__dirname, '../locale', locale, 'quantify.js'))) {
|
||
return '.'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns the relative path to a directory where
|
||
* a given "flavor" (short, long, narrow) labels file
|
||
* resides for a given locale.
|
||
* @param {string} locale
|
||
* @param {string} flavor
|
||
* @return {string} [directory]
|
||
*/
|
||
function findFlavorDirectory(locale, flavour) {
|
||
// Look in parent locales' folders.
|
||
// Example: `sr-Cyrl-BA` -> `sr-Cyrl` -> `sr`.
|
||
const parentLocale = findParentLocaleHavingFile(locale, `${flavour}.json`, { self: true })
|
||
if (parentLocale) {
|
||
if (parentLocale === locale) {
|
||
return '.'
|
||
}
|
||
return `../${parentLocale}`
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Some files are inherited from a parent locale to a child locale.
|
||
* For example, there are different locales: "ar" and "ar-AE".
|
||
* But their `quantify()` function is identical.
|
||
* Therefore, it's only stored inside `ar` folder
|
||
* and "ar-AE" locale reuses that file.
|
||
* @param {string} locale
|
||
* @param {string} fileName
|
||
* @param {object} [options]
|
||
* @param {boolean} [options.self] — Allow returning same `locale`.
|
||
* @param {function} [options.condition] — An extra condition imposed on the absolute file path of the file.
|
||
* @return {string} [locale]
|
||
*/
|
||
function findParentLocaleHavingFile(locale, fileName, options = {}) {
|
||
const restParts = locale.split('-')
|
||
const parts = []
|
||
let inheritFrom
|
||
while (restParts.length > 0) {
|
||
parts.push(restParts.shift())
|
||
const parentLocale = parts.join('-')
|
||
if (!options.self && parentLocale === locale) {
|
||
continue
|
||
}
|
||
if (!fs.existsSync(path.join(__dirname, '../locale', parentLocale))) {
|
||
continue
|
||
}
|
||
if (fs.existsSync(path.join(__dirname, '../locale', parentLocale, fileName))) {
|
||
if (!options.condition || options.condition(path.join(__dirname, '../locale', parentLocale, fileName))) {
|
||
inheritFrom = parentLocale
|
||
}
|
||
}
|
||
}
|
||
return inheritFrom
|
||
} |