Track language selection with Plausible Language Changed event.
Centralize UI language switches in cycleAppLanguage and document the event in plausible-events.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,40 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getNextLanguage, normalizeAppLanguage, SUPPORTED_LANGUAGES } from './i18nLanguages.js'
|
||||
import type { i18n as I18nInstance } from 'i18next'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { PlausibleEvents } from '../services/analytics.js'
|
||||
import {
|
||||
changeAppLanguage,
|
||||
cycleAppLanguage,
|
||||
getNextLanguage,
|
||||
normalizeAppLanguage,
|
||||
SUPPORTED_LANGUAGES
|
||||
} from './i18nLanguages.js'
|
||||
|
||||
const trackPlausibleEvent = vi.fn()
|
||||
|
||||
vi.mock('../services/analytics.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../services/analytics.js')>()
|
||||
return {
|
||||
...actual,
|
||||
trackPlausibleEvent: (...args: unknown[]) => trackPlausibleEvent(...args)
|
||||
}
|
||||
})
|
||||
|
||||
function createMockI18n(language: string): I18nInstance {
|
||||
let current = language
|
||||
return {
|
||||
language: current,
|
||||
changeLanguage: vi.fn(async (lng: string) => {
|
||||
current = lng
|
||||
;(this as { language: string }).language = lng
|
||||
})
|
||||
} as unknown as I18nInstance
|
||||
}
|
||||
|
||||
describe('i18nLanguages', () => {
|
||||
beforeEach(() => {
|
||||
trackPlausibleEvent.mockReset()
|
||||
})
|
||||
|
||||
it('normalizes regional tags to supported base codes', () => {
|
||||
expect(normalizeAppLanguage('de-DE')).toBe('de')
|
||||
expect(normalizeAppLanguage('nb-NO')).toBe('nb')
|
||||
@@ -18,4 +51,33 @@ describe('i18nLanguages', () => {
|
||||
expect(seen.size).toBe(SUPPORTED_LANGUAGES.length)
|
||||
expect(current).toBe('de')
|
||||
})
|
||||
|
||||
it('tracks explicit language changes', () => {
|
||||
const i18n = createMockI18n('de')
|
||||
changeAppLanguage(i18n, 'sv')
|
||||
|
||||
expect(i18n.changeLanguage).toHaveBeenCalledWith('sv')
|
||||
expect(trackPlausibleEvent).toHaveBeenCalledWith(PlausibleEvents.LANGUAGE_CHANGED, {
|
||||
from: 'de',
|
||||
to: 'sv'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not track when language stays the same', () => {
|
||||
const i18n = createMockI18n('en')
|
||||
changeAppLanguage(i18n, 'en')
|
||||
|
||||
expect(i18n.changeLanguage).not.toHaveBeenCalled()
|
||||
expect(trackPlausibleEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('cycleAppLanguage tracks the next language', () => {
|
||||
const i18n = createMockI18n('nb')
|
||||
cycleAppLanguage(i18n)
|
||||
|
||||
expect(trackPlausibleEvent).toHaveBeenCalledWith(PlausibleEvents.LANGUAGE_CHANGED, {
|
||||
from: 'nb',
|
||||
to: 'de'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { i18n as I18nInstance } from 'i18next'
|
||||
import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js'
|
||||
|
||||
/** Supported UI languages (ISO 639-1, language-only). */
|
||||
export const SUPPORTED_LANGUAGES = ['de', 'en', 'da', 'sv', 'nb'] as const
|
||||
|
||||
@@ -20,3 +23,17 @@ export function getNextLanguage(current?: string): AppLanguage {
|
||||
export function isGermanLocale(language?: string): boolean {
|
||||
return normalizeAppLanguage(language) === 'de'
|
||||
}
|
||||
|
||||
/** Switch UI language and track explicit user choice (not auto-detection). */
|
||||
export function changeAppLanguage(i18n: I18nInstance, language: AppLanguage): void {
|
||||
const from = normalizeAppLanguage(i18n.language)
|
||||
const to = normalizeAppLanguage(language)
|
||||
if (from === to) return
|
||||
|
||||
void i18n.changeLanguage(to)
|
||||
trackPlausibleEvent(PlausibleEvents.LANGUAGE_CHANGED, { from, to })
|
||||
}
|
||||
|
||||
export function cycleAppLanguage(i18n: I18nInstance): void {
|
||||
changeAppLanguage(i18n, getNextLanguage(i18n.language))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user