Skip to content

Instantly share code, notes, and snippets.

@Noitidart
Last active April 12, 2022 04:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Noitidart/110c2f859db62398ae76069f4a6c5642 to your computer and use it in GitHub Desktop.
Save Noitidart/110c2f859db62398ae76069f4a6c5642 to your computer and use it in GitHub Desktop.
_js-findClosestLocale - Gets the closest locale from the available locales
// rev3 - https://gist.github.com/Noitidart/110c2f859db62398ae76069f4a6c5642
/**
* Selects the closest matching locale from a list of locales.
*
* @param aLocales
* An array of available locales
* @param aMatchLocales
* An array of prefered locales, ordered by priority. Most wanted first.
* Locales have to be in lowercase.
* @return the best match for the currently selected locale
*
* Stolen from http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm
*/
function findClosestLocale(aLocales, aMatchLocales) {
aMatchLocales = aMatchLocales;
// Holds the best matching localized resource
let bestmatch = null;
// The number of locale parts it matched with
let bestmatchcount = 0;
// The number of locale parts in the match
let bestpartcount = 0;
for (let locale of aMatchLocales) {
let lparts = locale.split("-");
for (let localized of aLocales) {
let found = localized.toLowerCase();
// Exact match is returned immediately
if (locale == found)
return localized;
let fparts = found.split("-");
/* If we have found a possible match and this one isn't any longer
then we dont need to check further. */
if (bestmatch && fparts.length < bestmatchcount)
continue;
// Count the number of parts that match
let maxmatchcount = Math.min(fparts.length, lparts.length);
let matchcount = 0;
while (matchcount < maxmatchcount &&
fparts[matchcount] == lparts[matchcount])
matchcount++;
/* If we matched more than the last best match or matched the same and
this locale is less specific than the last best match. */
if (matchcount > bestmatchcount ||
(matchcount == bestmatchcount && fparts.length < bestpartcount)) {
bestmatch = localized;
bestmatchcount = matchcount;
bestpartcount = fparts.length;
}
}
// If we found a valid match for this locale return it
if (bestmatch)
return bestmatch;
}
return null;
}
@Noitidart
Copy link
Author

Noitidart commented Nov 23, 2016

README

Rev1

Rev2

  • Added rev and link comment

Rev3

  • Removed fall back to getPreferredLocales() as that isn't available

@eligrey
Copy link

eligrey commented Jan 19, 2022

Here's a more concise implementation of this utility using array methods:

const getAllSubLanguages = (langCode: string): string[] =>
  langCode
    .toLowerCase()
    .split('-')
    .flatMap((_, i, tags) => tags.slice(0, i + 1).join('-'))
    .reverse();

const getNearestSupportedLanguage = (
  preferred: string[],
  supported: string[],
): string | undefined =>
  supported.find((language) =>
    getAllSubLanguages(language).some((lang) =>
      preferred.some((preferredLang) =>
        preferredLang.toLowerCase() === lang
      )
    ),
  );

@Noitidart
Copy link
Author

Thanks for sharing @eligrey! I've also been using another way recently I'll share soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment