From 6a3094b244ea2d1477af47a63beed56696755d72 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 6 Apr 2026 13:28:57 +0100 Subject: [PATCH] fix: sort language dropdown alphabetically by native name (#7477) * fix: sort language dropdown alphabetically by native name Languages in the settings dropdown were ordered by language code, making it hard to find specific languages. Now sorted alphabetically by their native display name. Fixes #3263 Co-Authored-By: Claude Opus 4.6 (1M context) * test: verify language dropdown is sorted by native name Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- src/node/hooks/i18n.ts | 12 ++++++++++-- src/tests/backend/specs/i18n.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/tests/backend/specs/i18n.ts diff --git a/src/node/hooks/i18n.ts b/src/node/hooks/i18n.ts index a59de913f..a9adc190b 100644 --- a/src/node/hooks/i18n.ts +++ b/src/node/hooks/i18n.ts @@ -106,9 +106,17 @@ const getAllLocales = () => { // returns a hash of all available languages availables with nativeName and direction // e.g. { es: {nativeName: "espaƱol", direction: "ltr"}, ... } const getAvailableLangs = (locales:MapArrayType) => { - const result:MapArrayType = {}; + const unsorted:MapArrayType = {}; for (const langcode of Object.keys(locales)) { - result[langcode] = languages.getLanguageInfo(langcode); + unsorted[langcode] = languages.getLanguageInfo(langcode); + } + // Sort by native name so the language dropdown is alphabetical + const sorted = Object.entries(unsorted).sort(([, a]: any, [, b]: any) => + (a.nativeName || '').localeCompare(b.nativeName || '', undefined, {sensitivity: 'base'}) + ); + const result:MapArrayType = {}; + for (const [langcode, info] of sorted) { + result[langcode] = info; } return result; }; diff --git a/src/tests/backend/specs/i18n.ts b/src/tests/backend/specs/i18n.ts new file mode 100644 index 000000000..0f0b9be2a --- /dev/null +++ b/src/tests/backend/specs/i18n.ts @@ -0,0 +1,28 @@ +'use strict'; + +const assert = require('assert').strict; +const common = require('../common'); +const i18n = require('../../../node/hooks/i18n'); + +describe(__filename, function () { + before(async function () { + await common.init(); + }); + + it('availableLangs are sorted by nativeName (case-insensitive)', async function () { + const langs = i18n.availableLangs; + assert(langs != null, 'availableLangs should be populated after server init'); + + const nativeNames: string[] = Object.values(langs).map((info: any) => info.nativeName || ''); + assert(nativeNames.length > 1, 'expected more than one language'); + + for (let i = 1; i < nativeNames.length; i++) { + const cmp = nativeNames[i - 1].localeCompare(nativeNames[i], undefined, {sensitivity: 'base'}); + assert( + cmp <= 0, + `languages not sorted: "${nativeNames[i - 1]}" should come before "${nativeNames[i]}" ` + + `(index ${i - 1} vs ${i})`, + ); + } + }); +});