Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

s/lang/locale #1761

Merged
merged 7 commits into from Jul 25, 2014
Merged

s/lang/locale #1761

merged 7 commits into from Jul 25, 2014

Conversation

icambron
Copy link
Member

@icambron icambron commented Jul 5, 2014

  • Deprecated moment.lang(), moment.langData(), moment#lang, and
    duration#lang
  • Added moment.locale(), moment.localeData(), moment#localeData, and
    druation#localeData
  • Added moment.defineLocale() that defines a locale but doesn't set it
    as the global locale (Instance-level locale leaks to other instances #1750)
  • Removed the hack in the language concatenator that set the language
    to English.
  • Refactored internal code to use moment#localeData instead of local
    functions, and set the language directly on the moment instance at
    creation.
  • Moved all the files and changed the build scripts so that everything
    lives is named "locale" instead of "lang", e.g. the locale files under
    the "locale" directory.
  • I did not include build-generated changes like component.json and
    the concated locale files, but I did inspect them to see that they look
    right. But I did remove the old ones wherever they won't be named correctly in the future.

lang : deprecate(
"moment().lang is deprecated. Use moment().locale instead.",
function (key) {
return this.locale(key);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this should actually return localeData() to be keep the old API, so I need to fix that. The new one, where moment().locale() just returns the name, but moment().localeData() returns the Locale instances, is more consistent with the behavior of the library-level functions, so this is just a matter of changing what the deprecation does.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.

@icambron
Copy link
Member Author

icambron commented Jul 6, 2014

Looks like I'll need to rebase. I'll do that after I get some feedback.

@@ -0,0 +1,18 @@
0 info it worked if it ends with ok
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this file :)

@ichernev
Copy link
Contributor

ichernev commented Jul 8, 2014

Looks great. You can merge after the rebase.

 * Deprecated moment.lang(), moment.langData(), moment#lang, and
 duration#lang

 * Added moment.locale(), moment.localeData(), moment#localeData, and
 druation#localeData

 * Added moment.defineLocale() that defines a locale but doesn't set it
 as the global locale (moment#1750)

 * Removed the hack in the language concatenator that set the language
 to English.

 * Refactored internal code to use moment#localeData instead of local
 functions, and set the language directly on the moment instance at
 creation.

 * Moved all the files and changed the build scripts so that everything
 lives is named "locale" instead of "lang", e.g. the locale files under
 the "locale" directory.

 * I did *not* include build-generated changes like component.json and
 the concated locale files, but I did inspect them to see that they look
 right.
@icambron
Copy link
Member Author

@ichernev Fixed except for the one additional comment I made.

@ichernev
Copy link
Contributor

OK, so lets figure out the case with the default language:

If you don't include any language files

Use English. Not that you can use anything else

If you have included only one language file

Option 1: Use that language
Option 2: Use English and set language manually (breaks existing behavior and is kind of unfriendly)

If you have included more than one file

Option 1: Use last included language (consistent with the case with one language)
Option 2: Use English (existing UX, if you're using the bundle or manually bundle a few files). I also think it makes more sense.

To make everybody happy we can have a flag just_load: true, that you pass to the module, so it won't be set as default. It can be used by the bundled script, and by hard core users. For everybody else it will set as default. To make it backwards compatible, it will always bundle (but remember old setting), and return a function for unbundling (if you pass just_load: true).

What do you think?

@icambron
Copy link
Member Author

I definitely think that if we pick option 2, we have to pick it in both cases. Otherwise it's too complicated to explain.

I'm not sure I followed that last part:

To make it backwards compatible, it will always bundle (but remember old setting), and return a function for unbundling (if you pass just_load: true).

I guess that means this?

var backToOldLocale = require('locale/fr', {just_load: true}); //or however you pass options to require
moment.locale(); //=> 'fr'
backToOldLocale();
moment.locale(); //=> 'en'

If so, I'm not a huge fan:

  • How does it work in AMD?
  • The function only gets used in really specific places, but I get a bit queezy when you keep around closures with side effects like that.
  • The instance-level locale() setter uses just_load in its require() and then calls the unbundler right away? That's a bit odd, but I guess it's OK, so nevermind this point.
  • Does this require code in every locale file? I think it would.

I don't think there's anything really bad about making the user call moment.locale() in all cases. I think it's a lot better than random files trouncing the settings. We just need to be good about communicating the breaking change.

@icambron
Copy link
Member Author

@ichernev Removed those tests.

@icambron
Copy link
Member Author

@ichernev another issue. From your comment in #1750:

moment.lang('fr');  // sets the default for all newly created moments
                    // unless a language has been explicitly specified
                    // in the constructor

That is also intuitive to me and how I made this PR behave without even thinking about it. But I just realized from the discussion and tests in #1785, that that's actually not how moment is designed to behave; moment.lang('fr') sets all existing moments to French if they haven't been explicitly set.

> var moment = require('./moment');
> var inEnglish = moment()
> moment.lang('fr')
'fr'
> inEnglish.lang()._abbr
'fr'

We don't have great tests for that, but it now occurs to me that the code (before this PR), goes out of its way to ensure that, and our cloning logic even takes it into account.

It sort of makes sense; we're setting the language on the prototype, and it's thus being inherited everywhere. And if you're programmatically switching languages in an entire UI, you could conceivably want to set all of the locales on all of the moment objects you have sitting around as UI state. But in another sense it's totally wack, because all this prototype stuff is hidden away as implementation details, and they don't have a way to tell between moment() and moment().locale('en'). It just seems like we're mucking with all their objects. And it's unexpected enough that two of the maintainers didn't really think about it.

In this PR:

> var moment = require('./moment');
> var inEnglish = moment()
> inEnglish.locale()
'en'
> moment.locale('fr')
'fr'
> inEnglish.locale()
'en'

To me, that's on balance an improvement. But it is a big breaking change. What do you think?

/cc @timrwood, who probably has a better perspective on this.

@timrwood
Copy link
Member

/cc @timrwood, who probably has a better perspective on this.

Originally, the locale was just a global option you could set. It wasn't until 1.7.0 that you were able to set a locale on a instance rather than as a shared global.

I don't know if there is much of a use case for creating a moment without a locale and then expecting it to stay up to date with the global locale when the global locale changes. Looking back on it, it does feel a bit unexpected.

I think an additional deprecation note in the 'upgrade to 3.0' changelog would suffice.

@icambron
Copy link
Member Author

I agree.

As as an aside, if we make that change, we should move the global setting from moment.fn to moment, since the prototype won't be of use anymore.

@ichernev
Copy link
Contributor

just_load

OK, first about the just_load flag. The problem is people are requiring language files and expecting this to change the default language, so having them explicitly call locale sounds like a breaking change to me (not that bad after all).

I've seen other libraries do it more explicitly -- you take the main library, you take the language module, and then you tell the library to use the module. In this case you have full control over how exactly to load it (should it also switch or not).

So now the options are:

  • if you load a language, also set that language (existing behavior). I'd still split the code into two functions, now at least it would be clear from reading the lang file that it also sets the default.
  • make all language files not set the default (breaking change) -- but this should wait for 3.0. It will be a change in defineLocale. I guess we can add a warning if you create a moment without setting a locale manually first

lang existing behavior

To be honest, I never really understood how that worked until now. I've tried to follow the code a few times and every single time I gave up as it didn't make much sense (and I didn't have enough time). So I think changing it is something we can do, and it shouldn't break a lot of workflows. Also about the change being implemented with prototype -- it should be easy to enable passing global as the language which would mean pick the global, whatever it is. So if you really want you can create transparent-language-moments but the default would not be that.

The problem is this PR is already pretty big, and if we have to change it back to prototype-hack-global-lang and then again after 3.0 -- makes no sense. Now we use .localeData() everywhere, so it should be easy to add the global option. For those using moment in super-reactive environments like angular, making the public facing moments use global lang, so you can change the locale dynamically sounds like a sane thing. For everybody else it should be a no-op.

@ichernev
Copy link
Contributor

Yeah, definitely use moment instead of moment.fn to store the global. Also duration objects should behave the same way as moment objects, not sure what is the story there.

@icambron
Copy link
Member Author

I'd prefer that the user sets the language explicitly; I don't like that the behavior of my code depends on things like what order I put the JS in, and the whole thing feels flimsy and non-obvious. But I do see that it's a real breaking change, so I can put in some hacks for now.

So my proposal on all things:

  1. Move the global setting from moment.fn to moment
  2. Just break compatibility for people who used the locale switcher to update existing moments on the fly and add tests accordingly (for durations too)
  3. Make defineLocale additionally set the locale
  4. Make sure the lazy loader unsets it so that 3 doesn't break our fix for Instance-level locale leaks to other instances #1750.
  5. Revert the removal of the en hack in the moment-with-locales file.
  6. Write a ticket to remove 3-5 for 3.0
  7. Add global meta-locale as a separate PR

Sound good?

@ichernev
Copy link
Contributor

That makes sense. I hope its not too different than the current diff :)

This was referenced Jul 24, 2014
@icambron
Copy link
Member Author

@ichernev Done!

@ichernev
Copy link
Contributor

Looks great. Merging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants