Wednesday, November 17, 2021

fun with filters and locale selectors

For work I'm refactoring our locale selector. We have content that varies by country, and then is offered in different languages within each country. 

Our old way of providing a language picker tended to provide the country and language in the locale of the current page... but that's a huge matrix! You have list each country/language IN each of the separate languages. It's error prone: here's a page where our Belgium-in-French page lists the name of the Spanish language in English:

Also, the file to maintain that was over 7000 lines of JSON. (Yes I'm sure it compresses but still.)

Plus I don't believe it's best practice, as FlagsAreNotLanguages puts it:

If you’re linking to pages in German and Chinese, label them as ‘Deutsch’ and ‘中文’ — not ‘German’ and ‘Chinese’. 

It's more complicated when you have different content in different counties - but we are using Flags correctly, for the country and not the language :-) 

I made the pitch to our PO, with screenshots from Disney+ and Salesforce.com. (I had to cherry pick my examples, sites can be all over the place with this stuff.)

I found it a rather pleasant exercise to refactor the redundant JSON from 7300 lines to around 200. 

The other pleasant part was using JS/ECMAscript's new(ish) array operators - ... notation, filter, map, reduce, etc... they are such a good match with JSON overall, both are very expressive without being too arcane. I mulled over a few styles of keeping the lists sorted (wanting this to be roughly alphabetical, but having the current locale first, and then followed by other languages in the same country, then everything else.) I tried some weird ideas with sorting, but then realized I could just preserve the original locale order as listed in the JSON file, but filtered as:

    // finally put the current locale first, 
    // followed by other languages in same country, 
    // then everything else
    return [
      ...items.filter(exactLocaleMatch),
      ...items.filter(justCountryMatch),
      ...items.filter(noCountryMatch),
    ];

with little helper functions above that

    const exactLocaleMatch = (item: { id: string }) => 
        item.id === currentLocale;
  const justCountryMatch = (item: { id: string }) =>
    !exactLocaleMatch(item) && item.id.substring(3) 
        === currentLocale.substring(3);
  const noCountryMatch = (item: { id: string }) =>
    item.id.substring(3) !== currentLocale.substring(3);

Very satisfying work.

No comments:

Post a Comment