Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,17 +492,8 @@ To add a new locale:
},
```

4. Copy your translation file to `lunaria/files/` for translation tracking:

```bash
cp i18n/locales/uk-UA.json lunaria/files/uk-UA.json
```

> ⚠**Important:**
> This file must be committed. Lunaria uses git history to track translation progress, so the build will fail if this file is missing.

5. If the language is `right-to-left`, add `dir: 'rtl'` (see `ar-EG` in config for example)
6. If the language requires special pluralization rules, add a `pluralRule` callback (see `ar-EG` or `ru-RU` in config for examples)
4. If the language is `right-to-left`, add `dir: 'rtl'` (see `ar-EG` in config for example)
5. If the language requires special pluralization rules, add a `pluralRule` callback (see `ar-EG` or `ru-RU` in config for examples)

Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization#custom-pluralization) and [Plural Rules](https://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories) for more info.

Expand Down
7 changes: 5 additions & 2 deletions app/composables/useI18nStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ export function useI18nStatus() {
})

/**
* Whether the current locale is the source locale (English)
* Whether the current locale is the source locale (English) or a variant of it.
* The source locale is 'en' (base), but app-facing locale codes are 'en-US', 'en-GB', etc.
* We check if the current locale starts with the source locale code to handle variants.
*/
const isSourceLocale = computed(() => {
return locale.value === (status.value?.sourceLocale.lang ?? 'en-US')
const sourceLang = status.value?.sourceLocale.lang ?? 'en'
return locale.value === sourceLang || locale.value.startsWith(`${sourceLang}-`)
})

/**
Expand Down
6 changes: 0 additions & 6 deletions config/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,6 @@ const locales: (LocaleObjectData | (Omit<LocaleObjectData, 'code'> & { code: str
},
]

const lunariaJSONFiles: Record<string, string> = {}

function buildLocales() {
const useLocales = Object.values(locales).reduce((acc, data) => {
const locales = countryLocaleVariants[data.code]
Expand All @@ -369,12 +367,10 @@ function buildLocales() {
name: l.name,
files: [data.file as string, `${l.code}.json`],
}
lunariaJSONFiles[l.code] = l.country ? (data.file as string) : `${l.code}.json`
delete entry.file
acc.push(entry)
})
} else {
lunariaJSONFiles[data.code] = data.file as string
acc.push(data as LocaleObjectData)
}
return acc
Expand All @@ -385,8 +381,6 @@ function buildLocales() {

export const currentLocales = buildLocales()

export { lunariaJSONFiles }

export const datetimeFormats = Object.values(currentLocales).reduce((acc, data) => {
const dateTimeFormats = data.dateTimeFormats
if (dateTimeFormats) {
Expand Down
3 changes: 3 additions & 0 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const config: KnipConfig = {
/** Some components import types from here, but installing it directly could lead to a version mismatch */
'vue-router',

/** Required by @nuxtjs/i18n at runtime but not directly imported in production code */
'@intlify/shared',

/** Oxlint plugins don't get picked up yet */
'@e18e/eslint-plugin',
'eslint-plugin-regexp',
Expand Down
69 changes: 65 additions & 4 deletions lunaria.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,78 @@
import { defineConfig } from '@lunariajs/core/config'
import { locales, sourceLocale } from './lunaria/prepare-json-files.ts'
import type { Locale, Merge } from '@lunariajs/core'
import { currentLocales, countryLocaleVariants } from './config/i18n.ts'

// The source locale is `en` (en.json contains all reference translation keys).
// Country variants like `en-US` inherit from `en` via the merge config.
const sourceLocale: Locale = { label: 'English', lang: 'en' }

// Build the list of Lunaria locales from currentLocales.
// currentLocales has expanded codes (en-US, en-GB, ar-EG, es-ES, es-419, etc.)
// but NOT the base codes (ar, es) that the variants inherit from.
// We need to add those base codes as Lunaria locales too, so they can be
// referenced in the merge config and tracked independently.
const localeSet = new Set<string>()
const locales: Locale[] = []

for (const l of currentLocales) {
if (l.code === sourceLocale.lang || !l.name) continue
if (!localeSet.has(l.code)) {
localeSet.add(l.code)
locales.push({ label: l.name, lang: l.code })
}
}

// Add base language codes (ar, es, etc.) that aren't already in the list.
// These are the keys of countryLocaleVariants that aren't the source locale.
for (const baseLang of Object.keys(countryLocaleVariants)) {
if (baseLang === sourceLocale.lang) continue
if (!localeSet.has(baseLang)) {
// Use the first variant's name or the base code as label
const variants = countryLocaleVariants[baseLang]!
const label = variants[0]?.name ?? baseLang
localeSet.add(baseLang)
locales.push({ label, lang: baseLang })
}
}

if (locales.length === 0) {
throw new Error('No locales found besides source locale')
}

// Build merge config from countryLocaleVariants:
// Each variant locale merges keys from its base locale, so keys present in
// the base file count as covered for the variant.
// e.g. { 'en-US': ['en'], 'en-GB': ['en'], 'ar-EG': ['ar'], 'es-ES': ['es'], 'es-419': ['es'] }
const merge: Merge = {}
for (const [baseLang, variants] of Object.entries(countryLocaleVariants)) {
for (const variant of variants) {
// Each variant merges from its base language and (if not the source) implicitly
// from the source via normal Lunaria tracking.
const existing = merge[variant.code]
if (existing) {
existing.push(baseLang)
} else {
merge[variant.code] = [baseLang]
}
}
}

export default defineConfig({
repository: {
name: 'npmx-dev/npmx.dev',
},
sourceLocale,
locales,
locales: locales as [Locale, ...Locale[]],
files: [
{
include: ['lunaria/files/en-US.json'],
pattern: 'lunaria/files/@lang.json',
include: ['i18n/locales/en.json'],
pattern: 'i18n/locales/@lang.json',
type: 'dictionary',
merge,
optionalKeys: {
$schema: true,
vacations: true,
},
},
],
tracking: {
Expand Down
18 changes: 16 additions & 2 deletions lunaria/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export const TableBody = (
<tr>
<td>${Link(links.source(file.source.path), collapsePath(file.source.path))}</td>
${locales.map(({ lang }) => {
return TableContentStatus(file.localizations, lang, lunaria)
return TableContentStatus(file.localizations, lang, lunaria, file.type)
})}
</td>
</tr>`,
Expand All @@ -263,10 +263,24 @@ export const TableContentStatus = (
localizations: StatusEntry['localizations'],
lang: string,
lunaria: LunariaInstance,
fileType?: string,
): string => {
const localization = localizations.find(localization => localization.lang === lang)!
const isMissingKeys = 'missingKeys' in localization && localization.missingKeys.length > 0
const status = isMissingKeys ? 'outdated' : localization.status
// For dictionary files, status is determined solely by key completion:
// if there are missing keys it's "outdated", if all keys are present it's "up-to-date",
// regardless of git history. This prevents variants with merge coverage (e.g. en-US, en-GB)
// from showing as outdated when their keys are fully covered by the base locale.
const status =
fileType === 'dictionary'
? isMissingKeys
? 'outdated'
: localization.status === 'missing'
? 'missing'
: 'up-to-date'
: isMissingKeys
? 'outdated'
: localization.status
const links = lunaria.gitHostingLinks()
const link =
status === 'missing' ? links.create(localization.path) : links.source(localization.path)
Expand Down
Loading
Loading