Moment: en, en-US locales

Created on 24 Nov 2016  ·  42Comments  ·  Source: moment/moment

Just a question:

Why are there no 'en', 'en-US' locales?
I understand they have been taken as the 'default' value, and so, if that's what you want, you don't need to use locale to start with.
But, imagine you have the typical combo where the user can choose the locale.
And you are calling moment.locale() with the current value of that.
With the current situation, you have to special case the en/en-US case, which seems like a bit awkward to me.

Thx for your great work. Keep on pushing!

Most helpful comment

@icambron My environment is a React Native application using npm to control my dependencies, and I have been able to replicate the failure even in minimal sample applications. The problem is that in the loadLocale function, it attempts to require('./locale/' + name) where name is en-US, and that require fails. When that require fails in a release build, it causes the app to crash with a stack trace along the lines of the following:

E/ReactNativeJS: Requiring unknown module "./locale/en-us".
E/AndroidRuntime: FATAL EXCEPTION: mqt_native_modules
                                                                Process: com.myApp, PID: 30054
                                                                com.facebook.react.common.JavascriptException: Requiring unknown module "./locale/en-US"., stack:
                                                                o@2:742
                                                                n@2:426
                                                                i@2:278
                                                                t@2:210
                                                                Xe@648:16702
                                                                nt@648:17804
                                                                gt@648:22583
                                                                yt@648:22460
                                                                wt@648:23200
                                                                c@648:1112
                                                                parse@645:1536
                                                                render@643:2923
                                                                _renderValidatedComponentWithoutOwnerOrContext@141:7295
                                                                _renderValidatedComponent@141:7462
                                                                performInitialMount@141:3012
                                                                mountComponent@141:2056
                                                                mountComponent@132:205
                                                                performInitialMount@141:3162
                                                                mountComponent@141:2056
                                                                mountComponent@132:205
                                                                performInitialMount@141:3162
                                                                mountComponent@141:2056
                                                                mountComponent@132:205

Looking through the code, since getLocale sees that my key ('en-US') is not an array, it falls into loadLocale without ever trying to match 'en', which would only happen if it were to go into the chooseLocale function.

EDIT: After looking a bit more closely at the chooseLocale function, it would still first try to choose 'en-US' and then fail the require before ever trying 'en', so the failed require leading to a crashing app would still occur even if this was an array.
Also strange is the fact that the spot where it is breaking is inside a try/catch, yet causes a crash anyway. That is actually the really baffling thing here.

EDIT 2: Note that the require fails gracefully when in a debug (__DEV__) build and then it does fall down to using 'en'. I'm not sure why require is so much more explosive in __PROD__, but it is definitely breaking here in moment.

EDIT 3: From the require js docs, "If you do not express the dependencies, you will likely get loading errors since RequireJS loads scripts asynchronously and out of order for speed." So the try/catch is unlikely to actually save us here, since try/catch only really helps with synchronous errors.

All 42 comments

There is an en locale, it just doesn't come in a separate file. It is very much accepted by moment().locale() and moment.locale():

moment.locale('fr')
"fr"
moment().format("LLLL")
"vendredi 25 novembre 2016 03:39"
moment.locale('en')
"en"
moment().format("LLLL")
"Friday, November 25, 2016 3:39 AM"

en-US works too because we walk search for a less specific match and the en locale is US English:

moment.locale('en-GB')
"en-gb"
moment().format("LLLL")
"Friday, 25 November 2016 03:40"
moment.locale('en-US')
"en"
moment().format("LLLL")
"Friday, November 25, 2016 3:40 AM"

I don't buy it; consider the case where the locale is detected from the environment, and then a require dep path is constructed to load moment/locale/${detectedLocale}. If that variable is "en" or "en-us", then requirejs will fail to load a file(gets a 404). If it is detected as 'de', then the file will load.

@eigood Moment's locale loader knows it already has English and doesn't try to load it from an external file.

@icambron I don't think you are correct about walking down the tree when the user passes in 'en-US' as a locale. In fact, I am currently debugging through it and it is breaking because you are NOT walking down the tree.
My use case is that I am calling moment.utc(myDate, myFormat, locale, true) where locale is just a single string 'en-US'. Since that string cannot be imported (since there is no file for it) but it is not an array, it tries to require the locale, and then breaks in production environments.
It seems to me that the real best behavior would be to actually include en-US in the list of locales present by default (it is not there right now, just en on its own).

@steveccable Well, it does try to load it, just in case you had a more specific localization. When it fails, it tries just "en" and succeeds. I think the disconnect is just that somehow it failing to load "en-US" is breaking for you. Nothing is supposed to blow up here; it's just supposed to fail gracefully and move to the next option. So you'll have to tell me more about your environment: what, specifically, is going wrong?

@icambron My environment is a React Native application using npm to control my dependencies, and I have been able to replicate the failure even in minimal sample applications. The problem is that in the loadLocale function, it attempts to require('./locale/' + name) where name is en-US, and that require fails. When that require fails in a release build, it causes the app to crash with a stack trace along the lines of the following:

E/ReactNativeJS: Requiring unknown module "./locale/en-us".
E/AndroidRuntime: FATAL EXCEPTION: mqt_native_modules
                                                                Process: com.myApp, PID: 30054
                                                                com.facebook.react.common.JavascriptException: Requiring unknown module "./locale/en-US"., stack:
                                                                o@2:742
                                                                n@2:426
                                                                i@2:278
                                                                t@2:210
                                                                Xe@648:16702
                                                                nt@648:17804
                                                                gt@648:22583
                                                                yt@648:22460
                                                                wt@648:23200
                                                                c@648:1112
                                                                parse@645:1536
                                                                render@643:2923
                                                                _renderValidatedComponentWithoutOwnerOrContext@141:7295
                                                                _renderValidatedComponent@141:7462
                                                                performInitialMount@141:3012
                                                                mountComponent@141:2056
                                                                mountComponent@132:205
                                                                performInitialMount@141:3162
                                                                mountComponent@141:2056
                                                                mountComponent@132:205
                                                                performInitialMount@141:3162
                                                                mountComponent@141:2056
                                                                mountComponent@132:205

Looking through the code, since getLocale sees that my key ('en-US') is not an array, it falls into loadLocale without ever trying to match 'en', which would only happen if it were to go into the chooseLocale function.

EDIT: After looking a bit more closely at the chooseLocale function, it would still first try to choose 'en-US' and then fail the require before ever trying 'en', so the failed require leading to a crashing app would still occur even if this was an array.
Also strange is the fact that the spot where it is breaking is inside a try/catch, yet causes a crash anyway. That is actually the really baffling thing here.

EDIT 2: Note that the require fails gracefully when in a debug (__DEV__) build and then it does fall down to using 'en'. I'm not sure why require is so much more explosive in __PROD__, but it is definitely breaking here in moment.

EDIT 3: From the require js docs, "If you do not express the dependencies, you will likely get loading errors since RequireJS loads scripts asynchronously and out of order for speed." So the try/catch is unlikely to actually save us here, since try/catch only really helps with synchronous errors.

So after working through it a bit, I have a (less than ideal) workaround. Basically, whenever my code tries to pass in a locale of 'en-US', I intercept it and pass in a locale of 'en' instead. Other locales are allowed to pass through unchanged.

That said, I still think it is problematic that it can neither find en-US nor does it default to already having it, and you cannot include it specifically like you can with other locales to avoid the failure to find it when it is actually called upon.

@steveccable

Also strange is the fact that the spot where it is breaking is inside a try/catch, yet causes a crash anyway. That is actually the really baffling thing here.

Right, that's what I'm saying. I'm trying to understand why a failing require call would be catastrophic. I wouldn't help to hardcode in knowledge of en-US; we'd still have a similar failure in any other fallback case. i.e. that has nothing to do with en being a special case, but rather it has to do with the fallback process not working at all in your environment.

Working through it, it appears that React Native is not handling failed requires as gracefully as we might like. You are correct that it has nothing to do with en being a special case, and the only reason it seemed like it was special was because there was no way to prevent the en-US require from failing (since we can't import it directly like with other locales). As mentioned in the later edit, the try/catch doesnt work because it fails asynchronously.
For now, the workaround I mentioned above of just sanitizing locales of 'en-US' works for my purposes.

the try/catch doesnt work because it fails asynchronously

Oh, I missed that edit. That was going to be my guess too.

@steveccable
Not sure whether you still have the issue, but the exact same crashes in release build (although with the French locale) consumed a day of work for me 😢

E/ReactNativeJS: Requiring unknown module "./locale/fr".
(...)

I solved it with:

import 'moment/locale/fr';

Maybe it helps

The workaround of sanitising the locale input is ok, but it's really scary to know that a react native require() can fail asynchronously in any place and there is no way to catch that. Also, if i send a non-existing locale or a random string (say 'blah-Blah') that means the app crashes and i can't handle the error anywhere. So i have to be defensively checking around that nobody triggers this bug.

I got problems after version 2.19.2 here's what changed with that release https://github.com/moment/moment/compare/29afed6...328d51e the function updateLocale started using loadLocale internally which in turn use require with a dynamic string https://github.com/moment/moment/blob/fea48bb69eda8c0459915d6aa66a910a4d43a55b/moment.js#L1845

I guess this fails in react native cause there you can't require files dynamically by variable https://github.com/facebook/react-native/issues/6391

In my specific case, I'm importing the en-gb file so it should be found in locales[name] by the time I call moment.locale. I'm not sure why it falls through to require.

not sure that RN is a priority or not, but moment for RN will be broken for months.

With all this it seems like facebook#69 is the most productive fix to unblock everyone. I'll talk to @jeanlauliac and we'll see if we can merge it on Monday.

Just to give some background: There are multiple reasons why we do not support dynamic dependencies. First, static analysis breaks when dynamically requiring modules. Second, when using dynamic requires, the real module you are looking for may not be available. This can cause major issues in production that would prevent users from using our apps, like for example we could break Facebook. We had problems like this in the past and it isn't fun to deal with. Metro is intentionally opinionated to prevent people from doing things that will cause pain to users.

Finally, we understand this open source repo isn't in a great shape right now. The good news is that there have never been more people at FB working on Metro, and we have a number of major changes in store to make Metro a ton better, but the bad news is that there are pressing issues we need to work through unrelate to open source. We are hoping that in a couple of months this repo will be in a much better state and that it'll be easier to contribute to. Give us some time please.

For RN, I got it working in release mode by stripping to the first part of the locale (probably what moment does behind the screens when it fails to load the "xx-XX" locale). In the following snippet, locale is your device's locale in the form "xx-XX".

moment.locale(locale.indexOf("-") === -1 ? locale : locale.substr(0, locale.indexOf('-')))

This is weird behavior.

https://stackoverflow.com/a/47260841/175825 you need to import a specific locale for ecosystem to be able to consume them.

Because of this magic, I can't get react-big-calendar to display dates like mm/yy using BigCalendar.momentLocalizer() because it's expecting whatever locale is loaded.

I could open an issue with react-big-calendar, but what about the next lib I come across that's expecting a specific locale? And the next one?

Just add in en-us locale and default to magic.

Just add in en-us locale and default to magic.

It will not solve the problem, there are languages like Spanish (es-ES => es) that do automatic fallback.

For anyone who's still looking for a solution, this may help mitigate the issue (with the exception of a few edge cases) but won't fix the root issue of RN causing a crash during the try-catch require:

import moment from 'moment/min/moment-with-locales.min.js';

// locale is the locale detected by react-native-device-info
// ts is the time
export const formatTime = (locale, ts) => {
const m = moment.utc(ts);
locale = chooseMomentLocale(locale);
m.locale(locale);
// other logic to determine whether to show the time as "9:43 pm", "yesterday", or "7/8/2018"

return formattedTime;
}

/**
 * Checks if the device locale is available in moment and returns the locale to be loaded
 * @param  {string} locale locale to be checked
 * @return {string}        locale to be loaded by moment
 */
const chooseMomentLocale = (locale) => {
  // make the locale lower case 
  // will fix crashes caused by "en-GB" (instead of "en-gb") not being found
  locale = locale.toLowerCase();  
  if (moment.locales().includes(locale)) { // check if the locale is included in the array returned by `locales()` which (in this case) tells us which locales moment will support
    return locale;
  } else if (moment.locales().includes(locale.substring(0, 2))) { 
    // check if the first two letters of the locale are included in the array returned by `locales()` which (in this case) tells us which locales moment will support
    // will fixes crashes caused by "en-US" not being found, as we'll tell moment to load "en" instead
    return locale.substring(0, 2);
  }
    // use "en-gb" (the default language and locale for my app) as a fallback if we can't find any other locale
    return 'en-gb'; 
};

I also experienced this issue using React Native and was able to avoid it by including all locales alongside Moment, i.e.

import moment from 'moment'
import 'moment/min/locales'

(Alternatively, moment/min/moment-with-locales is also provided but it wasn't compatible with some of our other formatters.)

This is a super annoying issue.

Isn't the whole point of locales that you yon't even have to think about it, and certainly not be hardcoding 'en-US' into your code just because YOU are in US? I don't understand why when requesting a locale of 'en-US' it can't just take it. Causes people no end of headaches.

I solved this problem a year ago but completely forgot how, then I have to start all over. (I'm using a date picker in Angular and just putting the minimum implementation in blows up with this error).

I'm getting this in Angular trying to use the datetime picker. If I set the LOCALE to something like en-GB it works fine but without specifying anything I get this. I wish I could figure out the proper way to fix this

image

For RN, I got it working in release mode by stripping to the first part of the locale (probably what moment does behind the screens when it fails to load the "xx-XX" locale). In the following snipped, locale is your device's locale in the form "xx-XX".

moment.locale(locale.indexOf("-") === -1 ? locale : locale.substr(0, locale.indexOf('-')))

This solution works for me, using i18n and momentjs like this:

// imports:
import moment from 'moment';
import i18n from './i18n';
import 'moment/locale/fr';

// constructor: 
moment.locale(i18n.locale.indexOf('-') === -1 ? i18n.locale : i18n.locale.substr(0, i18n.locale.indexOf('-')));

I am still experimenting this issue running the version "moment": "^2.22.2",
Any correction in the last version?

I'll chime in on this issue.

  1. treating en-US as some hidden default different form the others is bad design.
  2. with no en-us locale, I don't know which CDN version to use.
  3. Moment looks impressive, but ill find another solution as en-US not being the same as other locales is a bad code smell.

I also had this error in react-native on production after making a nice development built that works perfectly.
Solved it with the solution @adesmet proposed and the following refactor:

function parseLocaleForMoment (language) {
  if (language.indexOf('-') === -1) return language
  else return language.substr(0, language.indexOf('-'))
}

Looks like RN sucks with moment so much that it's easier to just write a native formatter. Probably just one line of code, bullet proof to work.

Ended up fixing it like this:

const subLocales = [
  'ar-tn',
  'ar-dz',
  'ar-kw',
  'ar-ma',
  'ar-sa',
  'ar-ly',
  'de-at',
  'de-ch',
  'en-sg',
  'en-au',
  'en-ca',
  'en-gb',
  'en-ie',
  'en-nz',
  'es-us',
  'es-do',
  'fr-ca',
  'fr-ch',
  'gom-latn',
  'hy-am',
  'pa-in',
  'pt-br',
  'sr-cyrl',
  'tl-ph',
  'tlh',
  'tet',
  'ms-my',
  'it-ch',
  'tzl',
  'tzm',
  'tzm-latn',
  'nl-be',
  'ug-cn',
  'uz-latn',
  'zh-cn',
  'zh-hk',
  'zh-tw'
];

let momentLocale = i18n.locale.toLowerCase();

if (!subLocales.includes(momentLocale)) {
  momentLocale = momentLocale.substring(0, 2);
}

moment.locale(momentLocale);

with react native @tapz sadly this seems to work on Android but on iOS sadly :/ I am having some trouble with zh locale for some reasons :/

That might be because zh and a few other locales are formatted differently to differentiate between Simplified and Traditional. See https://gist.github.com/jacobbubu/1836273

@rajivshah3 indeed I was having zh_Hant_HK which wasn't working with tapz method I am now using your method which seems the best way to deal with this problem as smooth as possible thanks for your post 👍

Same problem, in RN production mode only.

This seems to work fine for me.

import 'moment/locale/fr';
import 'moment/locale/es';
import 'moment/locale/de';
import 'moment/locale/en-gb';
import 'moment/locale/es-us';

const toMomentLocale = locale => {
  let momentLocale = locale;
  momentLocale = momentLocale.replace('_', '-').toLowerCase();
  momentLocale = ['en-gb', 'en-us'].includes(locale)
    ? momentLocale
    : momentLocale.split('-')[0];
  return momentLocale;
};

As long as the data is imported (or en) and it respects the case, it seems to work fine for us

This is genuinely ridiculous. Stripping out the -region section of the language code is not a solution.

1) You lose the regional specificity of the locale.
2) Even worse, that method will cause further crashes. Chinese (zh), for example, doesn't even exist in the MomentJS locales. Only zh-cn, zh-hk and zh-tw are defined. So if you're using the above code to strip out regions from the locale string, your app will immediately crash on most Chinese devices. Just a measly ~800 million smartphone users.

My temporary (and utterly batshit) workaround for React Native is this:

const locale = 'zh-cn'; // or en-US etc
const languageCode = moment.locale(locale.indexOf("-") === -1 ? locale : locale.substr(0, locale.indexOf('-'))); // now 'zh', 'en' etc. Thanks @adesmet 
let momentJsLocale;
switch(languageCode.toLowerCase()) {
  case 'zh':
    // No 'zh' locale exists in MomentJS. App will crash in production if used.
    momentJsLocale = 'zh-cn';
    break;
  case 'pa':
    momentJsLocale = 'pa-in';
    break;
  case 'hy':
    momentJsLocale = 'hy-am';
    break;
  default:
    momentJsLocale = languageCode.toLowerCase();
}
moment.locale(momentJsLocale);

@adammcarth Hi, do we have any news about moment locales fix for RN?

This just crashed on a release build right now using en-gb.

I get a similar problem problem as; @simeyla .

When I added Luxon together with timepicker issues are popping up and they appear to be related to the 'en-US' locale.

@steveccable Well, it does try to load it, just in case you had a more specific localization. When it fails, it tries just "en" and succeeds. I think the disconnect is just that somehow it failing to load "en-US" is breaking for you. Nothing is supposed to blow up here; _it's just supposed to fail gracefully_ and move to the next option. So you'll have to tell me more about your environment: what, specifically, is going wrong?

Another problem with handling 'en-US' or other valid locales with exceptions is when you are debugging and ask debugger to brake on exceptions, you get stuck on this one repeatedly.

There should be better and simpler way especially for such a large locale as the 'en-US'.
Eventually, these locales with possibility of large audience/usage should be checked and treated in front, for speed consideration.

I modified the solution from @slorber to be more robust. This reformats an incoming locale (e.g. "zh_CN" to "zh-cn") and checks if it's supported by your moment installation, falling back to the country, then falling back to en-us.

const toMomentLocale = (locale) => {
  let newLocale = locale.replace('_', '-').toLowerCase();
  let tryLocales = [newLocale, newLocale.split('-')[0]];
  for (let i = 0; i < tryLocales.length; i++) {
    if (moment.locales().indexOf(tryLocales[i]) >= 0) return tryLocales[i];
  }
  return 'en-us';
};

// use it like this:
let m = moment();
m.locale(toMomentLocale('zh_CN')).format('LLL'); // "2019年12月11日上午9点33分" -- used "zh-cn"
moment.locale(toMomentLocale('ja_JP'));
moment().format('LLL') // 2019年12月11日 09:34 -- falls back to "ja"

Same problem, in RN production IOS mode only.

This seems to work fine for me.

import moment from "moment";
import 'moment/locale/es';

Spent quite some time debugging this, thank you @jorodriguez for this

This is still an issue 😢

Hi guys,

I have created a tiny package for you -> https://github.com/tonix-tuft/moment-utl

npm install --save moment moment-utl

You can use the following to determine all the supported locales of moment without the need to load them in advance:

import { allSupportedLocales, allSupportedLocalesMap } from "moment-utl";

const locales = allSupportedLocales(); // As an array: ["af", "ar", "ar-dz", "ar-kw", "ar-ly", "ar-ma", "ar-sa", "ar-tn", "az", "be", "bg", ...,  "en", ..., "zh-tw"]

const localesMap = allSupportedLocalesMap(); // As an object: { "af": 1, "ar": 2, "ar-dz": 3, "ar-kw": 4, "ar-ly": 5, "ar-ma": 6, "ar-sa": 7, "ar-tn": 8, "az": 9, "be": 10, ..., "en": 27, "zh-tw": 128 };

moment-utl peer depends on moment, but it bundles all the moment locales
taking them from the moment/locale directory during its build a moment dev dependency package.

However, as moment is a peer dependency, if are using ES6 and wanna make sure that allSupportedLocales and allSupportedLocalesMap both use up-to-date locales with the moment version you have installed in your project alongside with moment-utl (e.g., if moment adds a new locale someday, then this should get you covered), then you can also regenerate those locales using the moment-utl-locales script provided by moment-utl.

You can use it with the npx command:

npm install -g npx    # Install npx if you don't have it yet
npx moment-utl-locales    # Run this command from your project's root folder

When using this script, just make sure to run it before you bundle your code with Rollup or Webpack (whichever you use), so it will generate an up-to-date locales array and object before your bundler comes across them and adds them to your final bundle.

I hope this helps!

In reply to @adammcarth, please check my answer here: https://github.com/tonix-tuft/moment-utl/issues/1#issuecomment-616088826

This issue has haunted the guys at Odoo and the suggested fix was to create the en-us file manually. Sigh.

What I don't understand is why moment tries loading the en-us file if, as told some comments above, the loader should know that it already has "en" and not dynamically import it.

Anyway it's still a nightmare for RN users. The worst part is that the issue only occurs in production builds.

Having a hidden default is a package of surprises...

image

This issue has haunted the guys at Odoo and the suggested fix was to create the en-us file manually. Sigh.

What I don't understand is why moment tries loading the en-us file if, as told some comments above, the loader should know that it already has "en" and not dynamically import it.

Anyway it's still a nightmare for RN users. The worst part is that the issue only occurs in production builds.

Having a hidden default is a package of surprises...

image

I'm going to get 👎 for this but my solution was swapping moment out for dayjs. Since they share the same function/API naming it was as simple as a find all + replace all for me.

Just sharing what I did for people who might wanna consider this.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tukusejssirs picture tukusejssirs  ·  16Comments

CoryDanielson picture CoryDanielson  ·  8Comments

electrobabe picture electrobabe  ·  48Comments

Brendan-Lucas picture Brendan-Lucas  ·  9Comments

usmonster picture usmonster  ·  13Comments