mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
9eff914aef
The locale validator demanded every CLDR plural category, so each new ICU plural key took the daily Crowdin sync red until a `many` branch was hand-added for Spanish and French. That branch can never render: `many` selects at a million and over in Spanish, French, Italian and Portuguese, and only for fractional counts in Czech and Slovak, while every catalog plural counts whole items well below a million and intl-messageformat falls back to `other` regardless. Require only the categories an integer count 0..1000 can select, which is the same set every integer below a million selects, and keep `other` always so the categories everyday counts reach stay mandatory. Drop a target message that still fails validation from the sparse catalog instead of failing the whole export, so one broken string can no longer block the other 23 languages for days. Report the rejections in the run log and the pull request body, and keep the retention guard fatal when a rejected message would replace a translation that is already in Git.
63 lines
2.5 KiB
JavaScript
63 lines
2.5 KiB
JavaScript
import { validateLocaleMessage, validateSlotCountMessage } from './locale-message-validation.mjs'
|
|
|
|
export function flattenCatalog(value, prefix = '', output = new Map()) {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new Error(`${prefix || 'catalog root'} must be a message object`)
|
|
}
|
|
|
|
for (const [key, child] of Object.entries(value)) {
|
|
const messageKey = prefix ? `${prefix}.${key}` : key
|
|
if (typeof child === 'string') output.set(messageKey, child)
|
|
else if (child && typeof child === 'object' && !Array.isArray(child)) flattenCatalog(child, messageKey, output)
|
|
else throw new Error(`${messageKey} must be a string or message object`)
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
function messageErrors({ key, locale, message, referenceMessage, slotCountKeys }) {
|
|
if (referenceMessage === undefined) return [`${locale}: unexpected key ${key}`]
|
|
|
|
const errors = []
|
|
if (message.length === 0) errors.push(`${locale}: empty message ${key}`)
|
|
if (message.includes('\u2014')) errors.push(`${locale}: Unicode em dash is not allowed in ${key}`)
|
|
if (/<[^>]+>/.test(message)) errors.push(`${locale}: HTML is not allowed in ${key}`)
|
|
errors.push(...validateLocaleMessage({ key, locale, message, referenceMessage }))
|
|
if (slotCountKeys.has(key)) errors.push(...validateSlotCountMessage({ key, locale, message }))
|
|
return errors
|
|
}
|
|
|
|
export function validateCatalogs({ catalogs, referencedKeys = new Set(), slotCountKeys = new Set() }) {
|
|
const reference = catalogs.get('en')
|
|
if (!reference) throw new Error('English reference catalog is required')
|
|
|
|
const errors = []
|
|
for (const key of referencedKeys) {
|
|
if (!reference.has(key)) errors.push(`en: missing key referenced in source ${key}`)
|
|
}
|
|
|
|
for (const [locale, catalog] of catalogs) {
|
|
for (const [key, message] of catalog) {
|
|
errors.push(...messageErrors({ key, locale, message, referenceMessage: reference.get(key), slotCountKeys }))
|
|
}
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
export function findInvalidTargetMessages({ catalogs, slotCountKeys = new Set() }) {
|
|
const reference = catalogs.get('en')
|
|
if (!reference) throw new Error('English reference catalog is required')
|
|
|
|
const invalid = []
|
|
for (const [locale, catalog] of catalogs) {
|
|
if (locale === 'en') continue
|
|
for (const [key, message] of catalog) {
|
|
const errors = messageErrors({ key, locale, message, referenceMessage: reference.get(key), slotCountKeys })
|
|
if (errors.length > 0) invalid.push({ locale, key, errors })
|
|
}
|
|
}
|
|
|
|
return invalid
|
|
}
|