Skip to content
Closed
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
7 changes: 7 additions & 0 deletions lib/utils/l10n.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ test('translation compressing and decompressing is working', async () => {
const { t } = await import('./l10n.ts')
expect(t('Names must not be empty.')).toMatchInlineSnapshot('"Namen dürfen nicht leer sein."')
})

// if a language is not available, it should fall back to the base language (e.g. ja-JP -> ja)
test('properly falls back to base language', async () => {
setLanguage('ja')
const { t } = await import('./l10n.ts')
expect(t('Names must not be empty.')).toMatchInlineSnapshot('"名前は空にできません。"')
})
26 changes: 18 additions & 8 deletions lib/utils/l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,30 @@

import type { GettextTranslation, GettextTranslationBundle } from '@nextcloud/l10n/gettext'

import { getLanguage } from '@nextcloud/l10n'
import { getGettextBuilder } from '@nextcloud/l10n/gettext'

const currentLanguage = getLanguage().replace('-', '_')
const gtBuilder = getGettextBuilder()
.detectLanguage()
.setLanguage(currentLanguage)

for (const data of __TRANSLATIONS__) {
// eslint-disable-next-line @stylistic/semi
const translations = __TRANSLATIONS__;

const languages = translations.map(({ language }) => language)
const useBaseLanguage = !languages.includes(currentLanguage)
for (const data of translations) {
const { language, translations } = data as { language: string, translations: GettextTranslation[] }
const bundle: GettextTranslationBundle = {
headers: {},
translations: {
'': Object.fromEntries(translations.map((translation: GettextTranslation) => [translation.msgid, translation])),
},
const needsRename = useBaseLanguage && currentLanguage === language.split('_')[0]

@Antreesy Antreesy Sep 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the split be the other way around? e.g. we have de.js and de_DE.js, but not de_AT.js; then:

  • useBaseLanguage is true
  • needsRename is false, because currentLanguage is de_AT and both language.split('_')[0] are de
  • better match should be more generic, so de and not de_DE?

I see how it works with current ja and available ja_JP though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more a future proof variant of hard renaming done in server:

lang_map = fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs

We can also just hardcode that map if that makes more sense

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See: #2620

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, didn't know it's from .tx/config

if (language === currentLanguage || language === 'en' || needsRename) {
const bundle: GettextTranslationBundle = {
headers: {},
translations: {
'': Object.fromEntries(translations.map((translation: GettextTranslation) => [translation.msgid, translation])),
},
}
gtBuilder.addTranslation(needsRename ? currentLanguage : language, bundle)
}
gtBuilder.addTranslation(language, bundle)
}

const gt = gtBuilder.build()
Expand Down