Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
14 changes: 12 additions & 2 deletions src/components/GrampsjsMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import './GrampsjsMapOverlay.js'
import './GrampsjsMapMarker.js'
import './GrampsjsMapLayerSwitcher.js'
import './GrampsjsIcon.js'
import {fireEvent} from '../util.js'
import {fireEvent, normalizeOhmLocale} from '../util.js'
import {sharedStyles} from '../SharedStyles.js'
import {GrampsjsAppStateMixin} from '../mixins/GrampsjsAppStateMixin.js'

Expand Down Expand Up @@ -264,7 +264,17 @@ class GrampsjsMap extends GrampsjsAppStateMixin(LitElement) {
const theme = this.appState.getCurrentTheme()
const mapBaseStyle =
theme === 'dark' ? config.mapBaseStyleDark : config.mapBaseStyleLight
return style === 'base' ? mapBaseStyle : config.mapOhmStyle
if (style === 'ohm') {
const locale = this.appState.i18n?.lang
const ohmLocale = normalizeOhmLocale(locale)
// Log the language request for debugging purposes
// eslint-disable-next-line no-console
console.debug(
Comment thread
mahula marked this conversation as resolved.
Outdated
`OpenHistoricalMap: requesting labels in locale "${ohmLocale}" (frontend locale: "${locale}")`
)
return `${config.mapOhmStyle}?language=${ohmLocale}`
Comment thread
mahula marked this conversation as resolved.
Outdated
}
Comment thread
mahula marked this conversation as resolved.
return mapBaseStyle
Comment thread
mahula marked this conversation as resolved.
}
}

Expand Down
75 changes: 75 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -976,4 +976,79 @@ export function apiVersionAtLeast(dbInfo, major, minor, patch = 0) {
return pat >= patch
}

/**
* Mapping from frontend locale codes to OpenHistoricalMap-supported ISO 639-1 codes.
* OpenHistoricalMap does not support regional variants (e.g., de_AT, en_GB, pt_BR),
* so we map them to their base language codes.
* @see https://github.com/gramps-project/gramps-web/issues/493
*/
export const OHM_LOCALE_MAP = {
ar: 'ar',
ba: 'ba',
bg: 'bg',
br: 'br',
ca: 'ca',
cs: 'cs',
da: 'da',
de_AT: 'de',
de: 'de',
el: 'el',
en_GB: 'en',
en: 'en',
eo: 'eo',
es: 'es',
fi: 'fi',
fr: 'fr',
ga: 'ga',
he: 'he',
hr: 'hr',
hu: 'hu',
is: 'is',
id: 'id',
it: 'it',
ja: 'ja',
ko: 'ko',
lt: 'lt',
lv: 'lv',
mk: 'mk',
nb: 'nb',
nl: 'nl',
nn: 'nn',
pl: 'pl',
pt_BR: 'pt',
pt_PT: 'pt',
ro: 'ro',
ru: 'ru',
sk: 'sk',
sl: 'sl',
sq: 'sq',
sr: 'sr',
sv: 'sv',
ta: 'ta',
tr: 'tr',
uk: 'uk',
vi: 'vi',
zh_CN: 'zh',
zh_HK: 'zh',
zh_TW: 'zh',
}

/**
* Converts a frontend locale code to an OpenHistoricalMap-compatible ISO 639-1 code.
* Falls back to 'en' if the locale is unknown or unsupported.
* @param {string|undefined} locale - Frontend locale code (e.g., 'de_AT', 'en_GB')
* @returns {string} OHM-compatible language code (e.g., 'de', 'en')
*/
export function normalizeOhmLocale(locale) {
if (!locale) {
return 'en'
}
const mapped = OHM_LOCALE_MAP[locale]
if (mapped !== undefined) {
return mapped
}
const baseCode = locale.split('_')[0]
return baseCode || 'en'
Comment thread
mahula marked this conversation as resolved.
}

//
158 changes: 158 additions & 0 deletions test/unit/mapOhmLocale.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {describe, it, expect} from 'vitest'
import {normalizeOhmLocale, OHM_LOCALE_MAP} from '../../src/util.js'

describe('normalizeOhmLocale', () => {
describe('basic mapping', () => {
it('maps base language codes to themselves', () => {
expect(normalizeOhmLocale('de')).to.equal('de')
expect(normalizeOhmLocale('en')).to.equal('en')
expect(normalizeOhmLocale('fr')).to.equal('fr')
expect(normalizeOhmLocale('es')).to.equal('es')
expect(normalizeOhmLocale('it')).to.equal('it')
expect(normalizeOhmLocale('pl')).to.equal('pl')
expect(normalizeOhmLocale('ru')).to.equal('ru')
expect(normalizeOhmLocale('ja')).to.equal('ja')
expect(normalizeOhmLocale('zh')).to.equal('zh')
})

it('maps regional variants to base language codes', () => {
expect(normalizeOhmLocale('de_AT')).to.equal('de')
expect(normalizeOhmLocale('de_DE')).to.equal('de')
expect(normalizeOhmLocale('en_GB')).to.equal('en')
expect(normalizeOhmLocale('en_US')).to.equal('en')
expect(normalizeOhmLocale('pt_BR')).to.equal('pt')
expect(normalizeOhmLocale('pt_PT')).to.equal('pt')
expect(normalizeOhmLocale('zh_CN')).to.equal('zh')
expect(normalizeOhmLocale('zh_HK')).to.equal('zh')
expect(normalizeOhmLocale('zh_TW')).to.equal('zh')
})

it('maps all supported frontend locales listed in OHM_LOCALE_MAP', () => {
Object.entries(OHM_LOCALE_MAP).forEach(
([frontendLocale, expectedOhmLocale]) => {
const result = normalizeOhmLocale(frontendLocale)
expect(result).to.equal(expectedOhmLocale)
}
)
})
})

describe('fallback behavior', () => {
it('defaults to "en" when locale is undefined', () => {
expect(normalizeOhmLocale(undefined)).to.equal('en')
})

it('defaults to "en" when locale is null', () => {
expect(normalizeOhmLocale(null)).to.equal('en')
})

it('defaults to "en" when locale is empty string', () => {
expect(normalizeOhmLocale('')).to.equal('en')
})

it('extracts base code from unknown regional variants', () => {
expect(normalizeOhmLocale('xx_YY')).to.equal('xx')
expect(normalizeOhmLocale('zz_XY')).to.equal('zz')
})

it('defaults to "en" when locale is completely unknown with no base code', () => {
// Testing edge case with non-standard input
expect(normalizeOhmLocale('_')).to.equal('en')
expect(normalizeOhmLocale('')).to.equal('en')
})
})

describe('edge cases', () => {
it('handles locales with multiple underscores', () => {
// Should extract the part before the first underscore
expect(normalizeOhmLocale('sr__Latn')).to.equal('sr')
expect(normalizeOhmLocale('xx_Y_Z')).to.equal('xx')
})

it('handles locales not in the map with regional suffix', () => {
// If a locale isn't in the map but has a base code, use it
expect(normalizeOhmLocale('xy_AB')).to.equal('xy')
})
})
})

describe('OHM_LOCALE_MAP', () => {
it('contains entries for all frontendLanguages that need mapping', () => {
const frontendLanguages = [
'ar',
'ba',
'bg',
'br',
'ca',
'cs',
'da',
'de_AT',
'de',
'el',
'en_GB',
'en',
'eo',
'es',
'fi',
'fr',
'ga',
'he',
'hr',
'hu',
'is',
'id',
'it',
'ja',
'ko',
'lt',
'lv',
'mk',
'nb',
'nl',
'nn',
'pl',
'pt_BR',
'pt_PT',
'ro',
'ru',
'sk',
'sl',
'sq',
'sr',
'sv',
'ta',
'tr',
'uk',
'vi',
'zh_CN',
'zh_HK',
'zh_TW',
]
Comment thread
mahula marked this conversation as resolved.
Outdated

// All languages from frontendLanguages should either:
// 1. Be in OHM_LOCALE_MAP with a mapping to base code
// 2. Or map to themselves (base code = same code)
frontendLanguages.forEach(lang => {
const mapped = OHM_LOCALE_MAP[lang]
expect(mapped).to.not.be.undefined
})
})

it('maps all regional variants to their base language codes', () => {
// Verify specific important mappings
expect(OHM_LOCALE_MAP.de_AT).to.equal('de')
expect(OHM_LOCALE_MAP.en_GB).to.equal('en')
expect(OHM_LOCALE_MAP.pt_BR).to.equal('pt')
expect(OHM_LOCALE_MAP.pt_PT).to.equal('pt')
expect(OHM_LOCALE_MAP.zh_CN).to.equal('zh')
expect(OHM_LOCALE_MAP.zh_HK).to.equal('zh')
expect(OHM_LOCALE_MAP.zh_TW).to.equal('zh')
})

it('does not contain any entries that map to undefined', () => {
Object.entries(OHM_LOCALE_MAP).forEach(([locale, mappedLocale]) => {
expect(mappedLocale).to.be.a('string')
expect(mappedLocale.length).to.be.greaterThan(0)
})
})
})
Loading