Commit 3ab58f74 authored by Mark Florian's avatar Mark Florian

Merge branch 'leipert-better-absolute-date' into 'master'

Use configured browser locales for date formatting

See merge request gitlab-org/gitlab!65740
parents 5f613b77 ce46da1b
......@@ -2,7 +2,10 @@ import Jed from 'jed';
import ensureSingleLine from './ensure_single_line';
import sprintf from './sprintf';
const languageCode = () => document.querySelector('html').getAttribute('lang') || 'en';
const GITLAB_FALLBACK_LANGUAGE = 'en';
const languageCode = () =>
document.querySelector('html').getAttribute('lang') || GITLAB_FALLBACK_LANGUAGE;
const locale = new Jed(window.translations || {});
delete window.translations;
......@@ -50,13 +53,53 @@ const pgettext = (keyOrContext, key) => {
return translated[translated.length - 1];
};
/**
* Filters navigator languages by the set GitLab language.
*
* This allows us to decide better what a user wants as a locale, for using with the Intl browser APIs.
* If they have set their GitLab to a language, it will check whether `navigator.languages` contains matching ones.
* This function always adds `en` as a fallback in order to have date renders if all fails before it.
*
* - Example one: GitLab language is `en` and browser languages are:
* `['en-GB', 'en-US']`. This function returns `['en-GB', 'en-US', 'en']` as
* the preferred locales, the Intl APIs would try to format first as British English,
* if that isn't available US or any English.
* - Example two: GitLab language is `en` and browser languages are:
* `['de-DE', 'de']`. This function returns `['en']`, so the Intl APIs would prefer English
* formatting in order to not have German dates mixed with English GitLab UI texts.
* If the user wants for example British English formatting (24h, etc),
* they could set their browser languages to `['de-DE', 'de', 'en-GB']`.
* - Example three: GitLab language is `de` and browser languages are `['en-US', 'en']`.
* This function returns `['de', 'en']`, aligning German dates with the chosen translation of GitLab.
*
* @returns {string[]}
*/
export const getPreferredLocales = () => {
const gitlabLanguage = languageCode();
// The GitLab language may or may not contain a country code,
// so we create the short version as well, e.g. de-AT => de
const lang = gitlabLanguage.substring(0, 2);
const locales = navigator.languages.filter((l) => l.startsWith(lang));
if (!locales.includes(gitlabLanguage)) {
locales.push(gitlabLanguage);
}
if (!locales.includes(lang)) {
locales.push(lang);
}
if (!locales.includes(GITLAB_FALLBACK_LANGUAGE)) {
locales.push(GITLAB_FALLBACK_LANGUAGE);
}
return locales;
};
/**
Creates an instance of Intl.DateTimeFormat for the current locale.
@param formatOptions for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
@returns {Intl.DateTimeFormat}
*/
const createDateTimeFormat = (formatOptions) => Intl.DateTimeFormat(languageCode(), formatOptions);
const createDateTimeFormat = (formatOptions) =>
Intl.DateTimeFormat(getPreferredLocales(), formatOptions);
/**
* Formats a number as a string using `toLocaleString`.
......
......@@ -176,7 +176,7 @@ You can select your preferred time format for the GitLab user interface:
- Relative times, for example, `30 minutes ago`.
- Absolute times, for example, `May 18, 2021, 3:57 PM`.
The times are formatted depending on your chosen language.
The times are formatted depending on your chosen language and browser locale.
To set your time preference:
......
import { setLanguage } from 'helpers/locale_helper';
import { createDateTimeFormat, formatNumber, languageCode } from '~/locale';
import { createDateTimeFormat, formatNumber, languageCode, getPreferredLocales } from '~/locale';
describe('locale', () => {
afterEach(() => setLanguage(null));
......@@ -18,13 +18,91 @@ describe('locale', () => {
});
});
describe('getPreferredLocales', () => {
beforeEach(() => {
// Need to spy on window.navigator.languages as it is read-only
jest
.spyOn(window.navigator, 'languages', 'get')
.mockReturnValueOnce(['en-GB', 'en-US', 'de-AT']);
});
it('filters navigator.languages by GitLab language', () => {
setLanguage('en');
expect(getPreferredLocales()).toEqual(['en-GB', 'en-US', 'en']);
});
it('filters navigator.languages by GitLab language without locale and sets English Fallback', () => {
setLanguage('de');
expect(getPreferredLocales()).toEqual(['de-AT', 'de', 'en']);
});
it('filters navigator.languages by GitLab language with locale and sets English Fallback', () => {
setLanguage('de-DE');
expect(getPreferredLocales()).toEqual(['de-AT', 'de-DE', 'de', 'en']);
});
it('adds GitLab language if navigator.languages does not contain it', () => {
setLanguage('es-ES');
expect(getPreferredLocales()).toEqual(['es-ES', 'es', 'en']);
});
});
describe('createDateTimeFormat', () => {
beforeEach(() => setLanguage('en'));
const date = new Date(2015, 0, 3, 15, 13, 22);
const formatOptions = { dateStyle: 'long', timeStyle: 'medium' };
it('creates an instance of Intl.DateTimeFormat', () => {
const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' });
const dateFormat = createDateTimeFormat(formatOptions);
expect(dateFormat).toBeInstanceOf(Intl.DateTimeFormat);
});
it('falls back to `en` and GitLab language is default', () => {
setLanguage(null);
jest.spyOn(window.navigator, 'languages', 'get').mockReturnValueOnce(['de-AT', 'en-GB']);
const dateFormat = createDateTimeFormat(formatOptions);
expect(dateFormat.format(date)).toBe(
new Intl.DateTimeFormat('en-GB', formatOptions).format(date),
);
});
it('falls back to `en` locale if browser languages are empty', () => {
setLanguage('en');
jest.spyOn(window.navigator, 'languages', 'get').mockReturnValueOnce([]);
const dateFormat = createDateTimeFormat(formatOptions);
expect(dateFormat.format(date)).toBe(
new Intl.DateTimeFormat('en', formatOptions).format(date),
);
});
it('prefers `en-GB` if it is the preferred language and GitLab language is `en`', () => {
setLanguage('en');
jest
.spyOn(window.navigator, 'languages', 'get')
.mockReturnValueOnce(['en-GB', 'en-US', 'en']);
const dateFormat = createDateTimeFormat(formatOptions);
expect(dateFormat.format(date)).toBe(
new Intl.DateTimeFormat('en-GB', formatOptions).format(date),
);
});
it('prefers `de-AT` if it is GitLab language and not part of the browser languages', () => {
setLanguage('de-AT');
jest
.spyOn(window.navigator, 'languages', 'get')
.mockReturnValueOnce(['en-GB', 'en-US', 'en']);
expect(dateFormat.format(new Date(2015, 6, 3))).toBe('July 3, 2015');
const dateFormat = createDateTimeFormat(formatOptions);
expect(dateFormat.format(date)).toBe(
new Intl.DateTimeFormat('de-AT', formatOptions).format(date),
);
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment