Spis Treści
- Wprowadzenie
- Sortowanie z uwzględnieniem znaków diaktrycznych
- Formatowanie dat
- Tłumaczenie nazw
- Względne formatowanie dat
- Kod przykładów na Github
- Interaktywny przykład na Github Pages
- Podsumowanie
Wprowadzenie
Internacjonalizacja aplikacji niesie za sobą wiele wyzwań – różne języki w różny sposób formatują daty, liczby czy czas relatywny, są to rzeczy bardzo kontekstowe które nie zawsze da się rozwiązać z poziomu tłumaczeń np. z plików JSON. Wyświetlanie nazw państw, regionów czy jednostek administracyjnych również nastręcza problemów ponieważ jest tego bardzo dużo. Jest też potrzeba formatowania wyświetlania walut.
Choć trzymanie tłumaczeń plikach JSON nie zniknie zapewne nigdy, część przypadków da się rozwiązać natywnie – przy użyciu obiektu Intl. Obiekt ten zawiera metody zwracające obiekty które mogą tłumaczyć relatywny czas czy nazwy krajów w podanym języku, czym oszczędzą nam pracy przy tłumaczeniu aplikacji.
Sortowanie z uwzględnieniem znaków diaktrycznych
Intl.Collator
Problemem podczas sortowania alfabetycznie list jest to, że domyślne sortowanie nie uwzględnia specyficznych dla danego języka znaków diaktrycznych.
Np. w języku polskim litera ą
występuje po a
i przed b
. W innym językach potrafi być jeszcze ciekawiej – w języku niemieckim ä jest po a a w szwedzkim po z.
Z pomocą tutaj przychodzi nam obiekt Collator, który po utworzeniu udostępnia metodę compare
, potrafiącą radzić sobie ze znakami diaktrycznymi.
Przykład sortowania z użyciem Int.Collator.compare
Stwórzmy następującą strukturę HTML:
<div> <label for="collator-locale">Locale</label> <input list="collator-locales" type="text" id="collator-locale"> <datalist id="collator-locales"> <option>pl-PL</option> <option>en-GB</option> <option>en-US</option> </datalist> <br> <button id="collator-sort">Sort</button> <pre id="collator-result"></pre> <br> </div>
Struktura ta pozwoli wyświetlić nam rezultat sortowania tablicy w zależności od wybranego języka, z którym zainicjowany będzie Collator
.
Kod JavaScript prezentuje się następująco:
const arrayToSort = ['aa', 'ab', 'ąa', 'ba']; document.querySelector('#collator-result').innerHTML = JSON.stringify(arrayToSort); document.querySelector('#collator-sort').addEventListener('click', () => { const localArrayToSort = [...arrayToSort]; // sort modifies array instead of returning new array const locale = document.querySelector('#collator-locale').value; const compare = locale? new Intl.Collator(locale).compare : undefined; localArrayToSort.sort(compare); document.querySelector('#collator-result').innerHTML = JSON.stringify(localArrayToSort); });
Najpierw tworzymy tablicę którą chcemy sortować. Następnie pod kliknięcie przycisku Sort
podpinamy funkcję, która skopiuje główną tablicę, pobierze język dla którego chcemy tablicę posortować i posortuje elementy wg tego języka. Rezultat zostanie wpisany do odpowiedniego elementu w HTML.
Końcowy efekt przykładu prezentuje się następująco:
Podpowiedzi z użyciem elementu Datalist
Użycie elementu Datalist
który zawiera opcje wyboru pozwoli nam zaimplementować ciekawą funkcjonalność – będzie można wybrać element z listy która wyświetli się po kliknięciu w pole bądź wpisać ręcznie. Dodatkowo, lista ta będzie filtrowana znakami które już wpiszemy w polu.
Formatowanie dat
Intl.DateTimeFormat
Jeżeli chcielibyśmy sformatować datę w danym języku trzeba pamiętać o kilku kwestiach takich jak przetłumaczenie nazwy dnia tygodnia, miesiąca czy kolejność. Przykładowo w języku angielskim w odmianie brytyjskiej najpierw występuje dzień, potem miesiąc i rok. W odmianie amerykańskiej za to najpierw jest miesiąc, potem dzień i na końcu rok. Z pomocą przychodzi nam obiekt DateTimeFormat, który po utworzeniu udostępnia między innymi metody format
i formatRange
.
Przykład użycia Intl.DateTimeFormat
Do utworzenia daty użyjemy pola typu datetime-local, który umożliwia wybór zarówno daty jak i godziny. W przeglądarce Chrome prezentuje się on następująco:
Stwórzmy następujący kod HTML:
<label for="datetime-format-date">Date</label> <input id="datetime-format-date" type="datetime-local"> <br> <label for="datetime-format-locale">Locale</label> <input list="locales" type="text" id="datetime-format-locale"> <br> <button id="datetime-format-transform-date">Transform date</button> <pre id="datetime-format-transform-date-result"></pre>
UI który ten kod reprezentuje pozwoli nam wybrać datę wraz z godziną, ustawić język i po kliknięciu przycisku Transform date
wyświetlić datę przetłumaczoną na wybrany język.
Stwórzmy kod JS:
document.querySelector('#datetime-format-transform-date').addEventListener('click', () => { const date = new Date(document.querySelector('#datetime-format-date').value); const locale = document.querySelector('#datetime-format-locale').value; const dateTimeFormat = new Intl.DateTimeFormat(locale, {dateStyle: 'full', timeStyle: 'short'}); const formatted = dateTimeFormat.format(date); document.querySelector('#datetime-format-transform-date-result').innerHTML = formatted; });
Kod ten, po kliknięciu przycisku Transform date
pobiera datę i język. Następnie tworzy obiekt DateTimeFormat
z opcjami dzięki którymi wyświetlimy pełną datę – zawierającą nazwę miesiąca i dnia tygodnia oraz godzinę w formie krótkiej, bez strefy czasowej. Dla tego przykładu uproszczona konfiguracja wystarczy, mnogość opcji dostępna przy tworzeniu obiektu pozwoli na ewentualną szerszą konfigurację.
Efekt działania kodu wygląda następująco:
Sprawdźmy w jaki sposób przykład ten sformatuje datę dla brytyjskiej odmiany angielskiego:
Tłumaczenie nazw
Intl.DisplayNames
Jeśli chodzi o tłumaczenie nazw krajów, języków i walut pomoże nam obiekt utworzony przez konstruktor DisplayNames. Dane do przykładu pobierzemy z API udostępnianego przez RestCountries z którego pobierzemy listę krajów członkowskich Unii Europejskiej – konkretnie spod adresu https://restcountries.eu/rest/v2/regionalbloc/eu. Z obiektów które przyjdą w odpowiedzi na żądanie weźmiemy tylko kod kraju, jego języki oraz waluty. Dodatkowo dane wpiszemy do localStorage
, by nie nadużywać darmowego API.
Przykład użycia Intl.DisplayNames
Pomijając elementy które powtarzają się z poprzednich przykładów (pełny kod dostępny będzie na Github i na Github Pages w wersji live) czyli pole wyboru języka stwórzmy kod HTML który pozwoli wyświetlać dane krajów w tabelce:
<details> <summary>Show EU countries with languages and currencies</summary> <table id="display-names-table"> <thead> <tr> <th>Country</th> <th>Languages</th> <th>Currencies</th> </tr> </thead> <tbody> <tr> <td colspan="3">Click button to see translated data</td> </tr> </tbody> </table> </details>
Element details pozwoli nam stworzyć element w którym po kliknięciu jego zawartość się rozwinie i zobaczymy tabelkę z nazwą kraju, językami i walutami w nich używanymi.
Dane pobierzemy w następujący sposób:
if (!data) { const response = await fetch('https://restcountries.eu/rest/v2/regionalbloc/eu'); const body = await response.json(); data = body.map(el => ({ code: el.alpha2Code, languages: el.languages.map(el => el.iso639_1), currencies: el.currencies.map(el => el.code), }) ); localStorage.setItem(localStorageKey, JSON.stringify(data)); }
Po kliknięciu odpowiedniego przycisku utworzymy translatory:
const locale = document.querySelector('#display-names-locale').value; const namesTranslator = new Intl.DisplayNames(locale, { type: 'region' }); const languageTranslator = new Intl.DisplayNames(locale, { type: 'language' }); const currencyTranslator = new Intl.DisplayNames(locale, { type: 'currency' });
Następnie przy pomocy metody of
przetłumaczymy nazwę kraju, jego języki i waluty i przemapujemy eja wiersze tabeli. Pamiętać musimy by dla języków i walut użyć bloku try..catch
, ponieważ nie możemy być pewni że API zawsze zwróci kody obsługiwane przez DisplayNames
:
const tableRowsStr = data.map(el => { const languages = el.languages.map(el => { try { return languageTranslator.of(el); } catch (e) { return ''; } }).join(','); const currencies = el.currencies.map(el => { try { return currencyTranslator.of(el); } catch (e) { return ''; } }).join(','); return ( ` <tr> <td>${namesTranslator.of(el.code)}</td> <td>${languages}</td> <td>${currencies}</td> </tr> ` ); }).join(''); document.querySelector('#display-names-table').innerHTML = tableRowsStr;
Względne formatowanie dat
Intl.RelativeTimeFormat
Spójrzmy na screen z Messengera, który pokazuje daty otrzymania wiadomości sformatowane relatywnie:
Określenia typu X dni czy X godzin to właśnie formatowanie względne. Osiągnąć je możemy bez użycia bibliotek zewnętrznych dzięki RelativeTimeFormat.
Przykład Intl.RelativeTimeFormat
Stwórzmy przykład który pozwoli wybrać datę a następnie wyświetlić względną datę w stosunku do obecnej daty.
Utwórzmy kod HTML:
<label for="relative-time-date">Date</label> <input id="relative-time-date" type="datetime-local"> <br> <label for="relative-time-locale">Locale</label> <input list="locales" type="text" id="relative-time-locale"> <br> <button id="relative-time-transform">Transform to relative</button> <pre id="relative-time-transform-result"></pre>
A następnie kod JS który pobierze dane z pól, policzy w jakiej jednostce chcemy formatować i wyświetli te dane użytkownikowi:
const locale = document.querySelector('#relative-time-locale').value; const date = new Date(document.querySelector('#relative-time-date').value); const current = new Date(); let value; let unit; if (date.getFullYear() !== current.getFullYear()) { value = date.getFullYear() - current.getFullYear(); unit = 'year'; } else if (date.getMonth() !== current.getMonth()) { value = date.getMonth() - current.getMonth(); unit = 'month'; } else if (date.getDate() !== current.getDate()) { value = date.getDate() - current.getDate(); unit = 'day'; } else if (date.getHours() !== current.getHours()) { value = date.getHours() - current.getHours(); unit = 'hour'; } else if (date.getMinutes() !== current.getMinutes()) { value = date.getMinutes() - current.getMinutes(); unit = 'minute'; } const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto'}); document.querySelector('#relative-time-transform-result').innerHTML = formatter.format(value, unit);
Kod przykładów na Github
Pełny kod przykładu znajdziesz na:
https://github.com/radek-anuszewski/intl-example
Interaktywny przykład na Github Pages
Jeżeli chcesz sprawdzić działanie przykładu na żywo, znajdziesz go na stronie:
https://radek-anuszewski.github.io/intl-example/index.html
Podsumowanie
Choć Intl nie zastąpi klasycznej obsługi tłumaczeń, pomaga w kilku specyficznych przypadkach. Tematy typu formatowanie dat w danym języku czy względne daty potrafią być trudne w obsłudze i bardzo dobrze, że istnieją możliwości by obsłużyć te przypadki natywnie.