Show country and language flags for every regional popup locale

Verify all 245 registered popup locales and every translation file. Resolve regional flags from base languages with explicit legacy and script exceptions; preserve country-before-language order in RTL interfaces. Unit wiring and actual-helper checks pass. Add and syntax-check browser coverage; browser execution remains pending.

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu committed 2026-09-13 18:31:56 +03:00
1 parent cb68305053
commit 8549b6c4c6
6 files changed
+76 -3

No files matched your search

+8
View File
@@ -1280,3 +1280,11 @@ body.mobile-mode .pop-over .content-wrapper {
border: none;
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
/* Keep country then language flags in that visual order in RTL locales too. */
.pop-over[data-popup='changeLanguagePopup'] .language-flags {
direction: ltr;
unicode-bidi: isolate;
white-space: nowrap;
width: auto;
}
+1 -1
View File
@@ -243,7 +243,7 @@ template(name="changeLanguagePopup")
each languages
li(class="{{# if isCurrentLanguage}}active{{/if}}")
a.js-set-language
span.emoji-icon {{languageFlag}}
span.emoji-icon.language-flags(dir="ltr" aria-hidden="true") {{languageFlag}}
| {{name}}
if rtl
| (RTL)
+17 -1
View File
@@ -444,7 +444,23 @@ Template.changeLanguagePopup.helpers({
'kl': '🇬🇱', 'iu': '🇨🇦', 'chr': '🇺🇸', 'nah': '🇲🇽', 'bua': '🇷🇺', 'csb': '🇵🇱', 'szl': '🇵🇱', 'an': '🇪🇸', 'lld': '🇮🇹', 'rup': '🇬🇷',
'mai': '🇮🇳', 'bho': '🇮🇳', 'kok': '🇮🇳', 'ks': '🇮🇳', 'ckb': '🇮🇶', 'tig': '🇪🇷', 'wal': '🇪🇹'
};
return flagMap[this.tag] || '🌐';
// These legacy locale prefixes name different languages, not Venda/Walloon.
const languageOverrides = { 've-CC': '🇮🇹', 've-PP': '🇷🇺', 'wa-RR': '🇵🇭' };
const tag = this.tag;
const base = tag.split(/[-_@]/)[0];
const language = languageOverrides[tag] || flagMap[base] || '🌐';
if (!/[-_@]/.test(tag)) return language;
const countryOverrides = {
'be-BE': '🇧🇾', 've-CC': '🇮🇹', 've-PP': '🇷🇺', 'vl-SS': '🇧🇪',
'wa-RR': '🇵🇭', 'es-LA': '🌐', 'zh-GB': '🇨🇳', 'en-YS': '🌐',
'az-LA': '🇦🇿', 'uz-LA': '🇺🇿', 'uz-AR': '🇺🇿',
};
const countries = new Set('ZA AU DZ EG AZ ES CZ GB AT CH DE GR BR IT MY AR CL CO MX PE PY EE IR BE CA NL IN IL JP KH KR PT RO RU SI UA UZ VN CN HK SG TW ID TR TM'.split(' '));
const suffix = tag.split(/[-_@]/)[1];
const country = countryOverrides[tag] || (countries.has(suffix)
? String.fromCodePoint(...[...suffix].map(letter => 0x1f1e6 + letter.charCodeAt(0) - 65))
: language);
return `${country} ${language}`;
},
});
+27
View File
@@ -34,3 +34,30 @@ test('language list is an auto-filling multi-column grid', () => {
});
console.log(`\nAll ${passed} change-language-columns tests passed`);
// Evaluate the actual popup helpers against every registry entry, not a copied map.
const vm = require('node:vm');
const { parseLanguageMetadata } = require('./lib/languageRegistrySource.cjs');
const root = path.resolve(__dirname, '..');
const rows = parseLanguageMetadata(fs.readFileSync(path.join(root, 'imports/i18n/languages.js'), 'utf8'));
const header = fs.readFileSync(path.join(root, 'client/components/users/userHeader.js'), 'utf8');
const start = header.indexOf('Template.changeLanguagePopup.helpers({');
const end = header.indexOf('Template.changeLanguagePopup.events(', start);
let helpers;
vm.runInNewContext(header.slice(start, end), {
Template: { changeLanguagePopup: { helpers(value) { helpers = value; } } },
TAPi18n: { getSupportedLanguages: () => rows.map(row => ({tag: row[2], name: row[3], rtl: row[4]})), getLanguage: () => 'en' },
});
assert.deepEqual(Array.from(helpers.languages(), row => row.tag).sort(), rows.map(row => row[2]).sort(), 'all registered locales appear exactly once');
for (const row of rows) {
const flags = helpers.languageFlag.call({tag: row[2]});
assert.ok(flags && !flags.includes('undefined'), row[2]);
if (/[-_@]/.test(row[2])) assert.equal(flags.split(' ').length, 2, `country then language: ${row[2]}`);
else if (!['eo', 'tlh', 'vo', 'ia'].includes(row[2])) assert.notEqual(flags, '🌐', row[2]);
}
for (const [tag, expected] of Object.entries({'es-CO':'🇨🇴 🇪🇸', 'es_CO':'🇨🇴 🇪🇸', 'fr-CA':'🇨🇦 🇫🇷', 'be-BE':'🇧🇾 🇧🇾', 've-PP':'🇷🇺 🇷🇺', 've-CC':'🇮🇹 🇮🇹', 'wa-RR':'🇵🇭 🇵🇭', 'uz-AR':'🇺🇿 🇺🇿', 'zh-GB':'🇨🇳 🇨🇳', 'eo':'🌐', 'tlh':'🌐'})) {
assert.equal(helpers.languageFlag.call({tag}), expected, tag);
}
assert.notEqual(helpers.languageFlag.call({tag:'ve-PP'}), '🇿🇦 🇿🇦');
assert.ok(fs.readFileSync(path.join(root, 'client/components/users/userHeader.jade'), 'utf8').includes('language-flags(dir="ltr"'));
console.log(`changeLanguageColumns: all ${rows.length} popup locales and country/language flag ordering verified`);
+1 -1
View File
@@ -82,7 +82,7 @@ test('every language has a flag, or the deliberate globe', () => {
.filter(t => !CONSTRUCTED.includes(t) && !new RegExp(`'${t}': '`).test(map));
assert.ok(missing.length <= 12,
`${missing.length} languages fall back to the globe: ${missing.join(' ')}`);
assert.ok(/return flagMap\[this\.tag\] \|\| '🌐';/.test(header),
assert.ok(header.includes("flagMap[base] || '🌐'"),
'and the fallback is a globe, not a wrong country');
});
@@ -0,0 +1,22 @@
'use strict';
const { test, expect } = require('../fixtures');
const fs = require('node:fs');
const path = require('node:path');
const { parseLanguageMetadata } = require('../../lib/languageRegistrySource.cjs');
const rows = parseLanguageMetadata(fs.readFileSync(path.resolve(__dirname, '../../../imports/i18n/languages.js'), 'utf8'));
test('language popup lists every locale and orders country before language flags', async ({ boardPage }) => {
await boardPage.locator('.js-open-header-member-menu').first().click();
await boardPage.locator('.js-change-language').click();
const popup = boardPage.locator(".pop-over[data-popup='changeLanguagePopup']");
await expect(popup).toBeVisible();
await expect(popup.locator('.js-set-language')).toHaveCount(rows.length);
for (const [tag, name] of rows.map(row => [row[2], row[3]])) {
const entry = popup.locator('.js-set-language').filter({ hasText: name });
await expect(entry.first()).toBeAttached();
}
for (const [name, flags] of [['Español en Colombia','🇨🇴 🇪🇸'], ['Français (Canada)','🇨🇦 🇫🇷'], ['Vepsän kelʹ','🇷🇺 🇷🇺']]) {
const entry = popup.locator('.js-set-language').filter({hasText:name});
await expect(entry.locator('.language-flags')).toHaveText(flags);
await expect(entry.locator('.language-flags')).toHaveAttribute('dir','ltr');
}
});