-
Notifications
You must be signed in to change notification settings - Fork 28
i18n: Support accumulatively registering additional locale data for domain #105
Conversation
Codecov Report
@@ Coverage Diff @@
## master #105 +/- ##
=======================================
Coverage 64.54% 64.54%
=======================================
Files 41 41
Lines 598 598
Branches 117 117
=======================================
Hits 386 386
Misses 170 170
Partials 42 42
Continue to review full report at Codecov.
|
packages/i18n/src/index.js
Outdated
i18n.options.locale_data[ domain ] = localeData; | ||
i18n.options.locale_data[ domain ] = has( i18n.options.locale_data, domain ) | ||
? merge( {}, i18n.options.locale_data[ domain ], localeData ) | ||
: localeData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable to me, A second opinion might be good. I wonder if we do need the deep merge
or if it's possible to use just some Object.assign
or similar. I don't have enough knowledge about the locale data format to answer personally.
Also, should we add a unit test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got thinking about this, the other concern we might have here is protecting the [""]
index in the localeData
. We probably don't want plugins to trounce the locale_data[""]['domain']
property right? I'm not sure of what approach should be taken here yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably good argument for making sure Jed is ALWAYS instantiated with a domain
argument (even if its extracted from locale_data['']['domain']
if it exists and thus i18n.options.locale_data[domain]
will remain consistent with locale_data['']['domain']
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't want plugins to trounce the
locale_data[""]['domain']
property right?
What consequences would there be, out of curiosity?
I guess in my head I'm wondering, if a plugin developer erroneously set locale data for the default domain, with the merging behavior would it just be as though they're adding the existing set?
I'd be fine to be explicit with passing the argument, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I'm a bit confused with the behaviour of domain
across jed and wp.i18n
. There appears to be a lot of inconsistencies. "Domain" is defined:
- Within the locale_data itself via
localeData['']['domain']
- From the explicit
domain
argument forsetLocaleData
. - Within locale data as a toplevel property:
localeData[domain]
This seems kinda problematic to me, but may just be me not fully grokking the variations there.
Walking through what I understand (mostly for my own benefit), it looks like jed.textdomain is only set from localeData.domain
on construct and its simply considered the default value for domain in any i18n function calls when one is not explicitly provided for the function. When code calls somethign like __('some string', 'domain')
jed will end up looking for the string in locale_data[ 'domain' ]
.
So... running through a scenario.
If setLocaleData
is called with unique
in the domain argument, then regardless of what may be set in localeData['']['domain']
... the strings are assigned to locale_data['unique']
. Any calls to __('some string')
will have the string lookup in localeData['default']
, and any calls to __('some string', 'unique')
will have the string lookup in localeData['unique']
.
However, if in a subsequent call to setLocale
there is passed in unique
as the domain argument BUT it has this for the shape of the ''
index:
localeData[''].domain = 'some_other_domain'
...it looks like 'some_other_domain' is never really referenced anywhere by jed (its just ignored).
So long story short... ya looks like we don't have to worry about the index getting trounced when it comes to the value of localeData[''].domain
.
However there is the possibility of a plugin author messing up the plural_forms
or lang
properties if they are interacting with setLocale
directly and not going through an api we add to core. So I'm wondering if we should just add some protection for that, or should we bother? Or maybe its a feature and not an issue because that allows for changing lang for a domain on the fly (however in that case wouldn't we need the options.locale_data[domain]
index to be reset)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...it looks like 'some_other_domain' is never really referenced anywhere by jed (its just ignored).
Interesting. I guess it makes sense, since we're already kinda doing it wrong in Gutenberg:
<script type='text/javascript'>
wp.i18n.setLocaleData( {"":{"domain":"gutenberg","lang":"en_US"}} );
</script>
It's assigning data for the default
locale (by omitting the second argument), but its own localeData[ '' ].domain
is set to gutenberg
. Seems it should be default
, yeah?
However there is the possibility of a plugin author messing up the plural_forms or lang properties if they are interacting with setLocale directly and not going through an api we add to core. So I'm wondering if we should just add some protection for that, or should we bother?
I'm not entirely clear what you're referring to by "setLocale directly". In any case, I wouldn't overthink it too much, unless it's a footgun.
In any case, the remaining question I have here is if we should do as Riad originally suggested, and use Object.assign
to do the merge? Pulling in Lodash's merge
could be costly, when we don't need the deep merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems it should be default, yeah?
Yeah I agree. But since its ignored we probably don't even need to set [''].domain
.
I'm not entirely clear what you're referring to by "setLocale directly"
Well the statement I made is on the assumption that something will land in WordPress core that will automate queuing up wp.i18n.setLocale()
via wp_inline_script
or some other mechanism. But that wouldn't rule out some plugin/code calling wp.i18n.setLocale
directly within its own js (as opposed to indirectly through whatever api wp core exposes.
In any case, the remaining question I have here is if we should do as Riad originally suggested, and use Object.assign to do the merge? Pulling in Lodash's merge could be costly, when we don't need the deep merge.
Agreed, and I forgot about Riad's comment (thanks for the reminder). Pushed the change ⚡️
Added some additional unit tests, but I'm new to testing in js (and jest) so not sure if this is an acceptable way to test. |
packages/i18n/src/test/index.js
Outdated
//test setting additional localeData | ||
setLocaleData( additionalLocaleData, 'test_domain' ); | ||
|
||
describe( 'i18n' , () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's better if describes
are unique the i18n
is already defined above, I'd rename this one setLocaleData
and move it inside the previous i18n
describe. And probably wrap setLocaleData( additionalLocaleData, 'test_domain' );
in a beforeAll
inside the setLocaleData
describe.
Not sure that sentence is clear :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no the sentence wasn't entirely clear, but after a little googling I think I got the gist of what you wanted?
packages/i18n/src/index.js
Outdated
@@ -34,7 +35,9 @@ export function setLocaleData( localeData = { '': {} }, domain = 'default' ) { | |||
} ); | |||
} | |||
|
|||
i18n.options.locale_data[ domain ] = localeData; | |||
i18n.options.locale_data[ domain ] = has( i18n.options.locale_data, domain ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
has
is a deceptively simple function, one which as used here will invoke a very complex chain of calls to parse the domain
argument as a string path (and thus include in the module itself). I think we might be better off using Object#hasOwnProperty
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooo did not know that, good to know. I thought has might be different than hasIn
and hasIn
had the behaviour you described. I'll fix.
packages/i18n/src/index.js
Outdated
i18n.options.locale_data[ domain ] = localeData; | ||
i18n.options.locale_data[ domain ] = has( i18n.options.locale_data, domain ) | ||
? merge( {}, i18n.options.locale_data[ domain ], localeData ) | ||
: localeData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't want plugins to trounce the
locale_data[""]['domain']
property right?
What consequences would there be, out of curiosity?
I guess in my head I'm wondering, if a plugin developer erroneously set locale data for the default domain, with the merging behavior would it just be as though they're adding the existing set?
I'd be fine to be explicit with passing the argument, though.
packages/i18n/src/index.js
Outdated
@@ -35,7 +35,7 @@ export function setLocaleData( localeData = { '': {} }, domain = 'default' ) { | |||
} ); | |||
} | |||
|
|||
i18n.options.locale_data[ domain ] = has( i18n.options.locale_data, domain ) | |||
i18n.options.locale_data[ domain ] = domain in i18n.options.locale_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so @aduth I ended up using in
because it appears that's the most performant option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well to be more accurate, its the most performant option in firefox and chrome. In Safari hasOwnProperty
wins. I'm happy to use whatever you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's important to understand that in
has different behavior than hasOwnProperty
though, where the former traverses up through the prototype chain of the object.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
As implemented here, this is susceptible to errors / oddities when doing something like assigning valueOf
as a domain, or any other member of the object prototype.
'valueOf' in {}; // true
( {} ).hasOwnProperty( 'valueOf' ); // false
This line of code is likely to be called only a handful of times for an entire page session, so optimizing for performance here is a bit overkill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related: #85
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gotcha, makes sense, changed it.
packages/i18n/src/index.js
Outdated
@@ -34,7 +35,9 @@ export function setLocaleData( localeData = { '': {} }, domain = 'default' ) { | |||
} ); | |||
} | |||
|
|||
i18n.options.locale_data[ domain ] = localeData; | |||
i18n.options.locale_data[ domain ] = i18n.options.locale_data.hasOwnProperty(domain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code style†: Space between parentheses (reference
† This repository is not (yet) enforced by ESLint standards.
packages/i18n/src/test/index.js
Outdated
@@ -26,6 +26,10 @@ const localeData = { | |||
|
|||
"fruit\u0004%d apple" : [ "une pomme", "%d pommes" ], | |||
} | |||
const additionalLocaleData = { | |||
"cheeseburger" : ["hamburger au fromage"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packages/i18n/src/test/index.js
Outdated
@@ -84,4 +88,21 @@ describe( 'i18n', () => { | |||
expect( result ).toBe( 'bonjour Riad' ); | |||
} ); | |||
} ); | |||
|
|||
describe( 'setAdditionalLocale' , () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code style: No space before comma (reference)
packages/i18n/src/index.js
Outdated
@@ -34,7 +34,9 @@ export function setLocaleData( localeData = { '': {} }, domain = 'default' ) { | |||
} ); | |||
} | |||
|
|||
i18n.options.locale_data[ domain ] = localeData; | |||
i18n.options.locale_data[ domain ] = i18n.options.locale_data.hasOwnProperty( domain ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, one more question 😄
Do we care about mutating i18n.options.locale_data
?
Could skip an object clone to drop the first {}
argument of Object.assign
and assign directly.
Maybe as:
if ( i18n.options.locale_data.hasOwnProperty( domain ) ) {
Object.assign( i18n.options.locale_data[ domain ], localeData )
} else {
i18n.options.locale_data[ domain ] = localeData;
}
Direct assignment with =
is fine too, since Object.assign
returns the resulting target object.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or: We could simplify this to always assign into the {}
, even if ! i18n.options.locale_data.hasOwnProperty( domain )
, since it's fine if anything but the first argument of Object.assign
is undefined
:
i18n.options.locale_data[ domain ] = Object.assign( {}, i18n.options.locale_data[ domain ], localeData );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm fine either way. I wasn't sure if we should mutate the object directly or not but in my case its likely a force of habit working with react/redux that I just merge into a new object. So mutation should be okay here.
I like your last example so I'll roll with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to go after tabbing added.
packages/i18n/src/index.js
Outdated
@@ -34,7 +34,11 @@ export function setLocaleData( localeData = { '': {} }, domain = 'default' ) { | |||
} ); | |||
} | |||
|
|||
i18n.options.locale_data[ domain ] = localeData; | |||
i18n.options.locale_data[ domain ] = Object.assign( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These lines need to be tabbed once more.
See WordPress/gutenberg#6051 for use-case requiring this change.