From d08bbae770849ec7353327d54e1d40c676a6364b Mon Sep 17 00:00:00 2001 From: zbettenbuk Date: Mon, 25 Dec 2017 19:03:46 +0100 Subject: [PATCH] [RN] Add proper locale support to MomentJS --- react/features/recent-list/functions.js | 102 ++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/react/features/recent-list/functions.js b/react/features/recent-list/functions.js index 1721327ff..d78774bcd 100644 --- a/react/features/recent-list/functions.js +++ b/react/features/recent-list/functions.js @@ -2,10 +2,39 @@ import moment from 'moment'; +import { RECENT_URL_STORAGE } from './constants'; + import { i18next } from '../base/i18n'; import { parseURIString } from '../base/util'; -import { RECENT_URL_STORAGE } from './constants'; +/** + * MomentJS uses static language bundle loading, so in order to support + * dynamic language selection in the app we need to load all bundles that we + * support in the app. + * FIXME: If we decide to support MomentJS in other features as well + * we may need to move this import and the lenient matcher to the i18n feature. + */ +require('moment/locale/bg'); +require('moment/locale/de'); +require('moment/locale/eo'); +require('moment/locale/es'); +require('moment/locale/fr'); +require('moment/locale/hy-am'); +require('moment/locale/it'); +require('moment/locale/nb'); + +// OC is not available. Please submit OC translation +// to the MomentJS project. + +require('moment/locale/pl'); +require('moment/locale/pt'); +require('moment/locale/pt-br'); +require('moment/locale/ru'); +require('moment/locale/sk'); +require('moment/locale/sl'); +require('moment/locale/sv'); +require('moment/locale/tr'); +require('moment/locale/zh-cn'); /** * Retreives the recent room list and generates all the data needed to be @@ -21,6 +50,11 @@ export function getRecentRooms(): Promise> { const recentRoomDS = []; if (recentURLs) { + // we init the locale on every list render, so then it + // changes immediately if a language change happens + // in the app. + const locale = _getSupportedLocale(); + for (const e of JSON.parse(recentURLs)) { const location = parseURIString(e.conference); @@ -31,8 +65,11 @@ export function getRecentRooms(): Promise> { conference: e.conference, conferenceDuration: e.conferenceDuration, conferenceDurationString: - _getDurationString(e.conferenceDuration), - dateString: _getDateString(e.date), + _getDurationString( + e.conferenceDuration, + locale + ), + dateString: _getDateString(e.date, locale), dateTimeStamp: e.date, initials: _getInitials(location.room), room: location.room, @@ -76,12 +113,15 @@ export function updateRecentURLs(recentURLs: Array) { * Returns a well formatted date string to be displayed in the list. * * @param {number} dateTimeStamp - The UTC timestamp to be converted to String. + * @param {string} locale - The locale to init the formatter with. Note: This + * locale must be supported by the formatter so ensure this prerequisite + * before invoking the function. * @private * @returns {string} */ -function _getDateString(dateTimeStamp: number) { +function _getDateString(dateTimeStamp: number, locale: string) { const date = new Date(dateTimeStamp); - const m = moment(date).locale(i18next.language); + const m = _getLocalizedFormatter(date, locale); if (date.toDateString() === new Date().toDateString()) { // The date is today, we use fromNow format. @@ -96,12 +136,14 @@ function _getDateString(dateTimeStamp: number) { * length. * * @param {number} duration - The duration in MS. + * @param {string} locale - The locale to init the formatter with. Note: This + * locale must be supported by the formatter so ensure this prerequisite + * before invoking the function. * @private * @returns {string} */ -function _getDurationString(duration: number) { - return moment.duration(duration) - .locale(i18next.language) +function _getDurationString(duration: number, locale: string) { + return _getLocalizedFormatter(duration, locale) .humanize(); } @@ -115,3 +157,47 @@ function _getDurationString(duration: number) { function _getInitials(room: string) { return room && room.charAt(0) ? room.charAt(0).toUpperCase() : '?'; } + +/** + * Returns a localized date formatter initialized with the + * provided date (@code Date) or duration (@code Number). + * + * @private + * @param {Date | number} dateToFormat - The date or duration to format. + * @param {string} locale - The locale to init the formatter with. Note: This + * locale must be supported by the formatter so ensure this prerequisite + * before invoking the function. + * @returns {Object} + */ +function _getLocalizedFormatter(dateToFormat: Date | number, locale: string) { + if (typeof dateToFormat === 'number') { + return moment.duration(dateToFormat).locale(locale); + } + + return moment(dateToFormat).locale(locale); +} + +/** + * A lenient locale matcher to match language and dialect if possible. + * + * @private + * @returns {string} + */ +function _getSupportedLocale() { + const i18nLocale = i18next.language.toLowerCase(); + const localeRegexp = new RegExp('^([a-z]{2,2})(-)*([a-z]{2,2})*$'); + const localeResult = localeRegexp.exec(i18nLocale); + + if (localeResult) { + const currentLocaleRegexp = new RegExp( + `^${localeResult[1]}(-)*${`(${localeResult[3]})*` || ''}` + ); + + return moment.locales().find( + lang => currentLocaleRegexp.exec(lang) + ) || 'en'; + } + + // default fallback + return 'en'; +}