-
-
Notifications
You must be signed in to change notification settings - Fork 929
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
feat!: multi locale fallback #858
Conversation
locales: UsedLocales; | ||
locale: UsableLocale; | ||
localeFallback: UsableLocale; | ||
localeOrder: UsableLocale[]; |
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 assume we need to make these readonly, so we can control when setting 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.
There needs to be a way to set them, it may also be useful to get them.
Especially so, if you consider it in combination with seed.
const parts = locale.split('_'); | ||
for (let i = parts.length; i > 0; i--) { | ||
const subLocale = parts.slice(0, i).join('_'); |
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.
question: could you explain me whats going on here? if you would like via voice chat 🙂
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.
'en_AU_ocker'
-> ['en', 'AU', 'ocker']
// L177
-> ['en', 'AU', 'ocker']
-> 'en_AU_ocker'
// L179
-> ['en', 'AU']
-> 'en_AU'
// L179
-> ['en']
-> 'en'
// L179
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.
Oh... I see, and it will not cause issues, if a in-between locale could not be found cause it's not defined?
Also maybe it should be documented that this exists / handle like this
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.
Side note:
ES6 way:
'en_AU_ocker'.split('_').reduce((result, _, i, parts) => {
return result.concat(parts.slice(0, parts.length - i).join('_'))
}, [])
No split:
'en_AU_ocker'.split('_').reduce((result, _, i, parts) => {
parts.pop()
return result.concat(parts.join('_'))
}, ['en_AU_ocker'])
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.
Oh... I see, and it will not cause issues, if a in-between locale could not be found cause it's not defined?
Line 180 in 75ffa2e
if (this.locales[subLocale] != null) { |
I probably have to add some tests explicitly for that case.
Also maybe it should be documented that this exists / handle like this
I will try to do that.
Side note:
IMO my "old school" approach is way easier to read for me.
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.
IMO my "old school" approach is way easier to read for me.
Both fine with me. I just posted this for fun :) Possibly the no split version could be understood better then slice
, not sure...
Codecov Report
@@ Coverage Diff @@
## main #858 +/- ##
==========================================
+ Coverage 99.35% 99.38% +0.02%
==========================================
Files 1922 1922
Lines 183052 183034 -18
Branches 900 906 +6
==========================================
+ Hits 181878 181900 +22
+ Misses 1118 1078 -40
Partials 56 56
|
@@ -8,8 +8,7 @@ import ar from '../locales/ar'; | |||
import en from '../locales/en'; | |||
|
|||
const faker = new Faker({ | |||
locale: 'ar', | |||
localeFallback: 'en', | |||
localeOrder: ['ar', 'en'], |
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.
As en
is presented in every locale definition, I think we should skip it and add it as default on usage. Would make the config much simpler.
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.
#642
With that it will someday be possible to create your own Faker instance without 'en'
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 instead of configuring it explicitly you want to move it to a default / implicit value?
I explicitly didn't choose that approach to allow users to omit the en
locale to avoid certain fallback mechanism issues.
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 don't think this should be possible tbh. en
is the only full locale and mostly maintained. If you would like to create a new feature which needs locale, we can't block it until its implemented on every locale, right?
The only interface we should offer for locales should be:
const faker = new Faker({ locales: ['de_AT', 'fr'] })
which would mean following fallback line: de_AT > de > fr > en
I can't imagine someone would prefer not working feature over getting a string in English when its not supported in a locale selected by him.
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.
const faker = new Faker({ locales: ['de_AT', 'fr'] })
locales
is already in use and reserved for the locales itself that can be passed- That would resolve to
de_AT > de > fr
notde_AT > de > fr > en
, maybe you don't wanten
, if you want it, pass 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.
1
locales
is already in use and reserved for the locales itself that can be passed
I am not sure if I understand? This is exactly what we want, initialise faker with locales, right?
I think it should always add en
no matter what. Imagine user calls faker.animal.dog()
and there is no data for it in neither of the locales he specified. What should faker do then:
- return empty string?
- throw an error?
- or return something from
en
I would vote for 3. Better to return English name, than nothing. But maybe we should throw?
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.
1
locales
is already in use and reserved for the locales itself that can be passedI am not sure if I understand? This is exactly what we want, initialise faker with locales, right?
locales
-> available data; localeOrder
-> active data (+ their order)
I think it should always add
en
no matter what. Imagine user callsfaker.animal.dog()
and there is no data for it in neither of the locales he specified. What should faker do then:
We add it by default, to exactly ensure this behavior.
However, to avoid an issue like #547, we have to avoid hardcoded fallbacks.
Sometimes having an undefined/error is easier to detect, then a random english word.
See also #823
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.
locales -> available data; localeOrder -> active data (+ their order)
This is not intuitive at all.
Sometimes having an undefined/error is easier to detect, then a random english word.
That's true, but since faker is meant to provide fake data for tests and maybe some demoes, I would be surprised if someone would prefer error to be thrown instead of seeing the value in English... If the error is thrown he would be a bit blocked vs when English is returned and he might be annoyed a bit and report the issue to us, which we would encourage him to fix by providing localisation...
I spent some time thinking about it and I wonder why would someone want to switch locales if he can simply have to separate instances of faker for every locale he needs? Can you come up with sensible example when switching locales on single version of faker is useful?
I would rather see something like this on the user side:
import { Faker, pl, de_AT } from '@faker-js/faker'
// or for someone who does not have tree-shaking bundler
import { Faker } from '@faker-js/faker'
import { pl } from '@faker-js/faker/locale/pl'
import { de_DE } from '@faker-js/faker/locale/de_DE'
const faker = {
pl: new Faker({ locales: [pl] })
de: new Faker({ locales: [de_AT] })
}
console.log(faker.pl.animal.bird())
console.log(faker.de.animal.bird())
That's clean.
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.
Can you come up with sensible example when switching locales on single version of faker is useful?
Because its easier, otherwise we wouldn't do that in our tests.
locales -> available data; localeOrder -> active data (+ their order)
This is not intuitive at all.
For me it is, also I don't want to change the base logic too much with this PR.
(I intend to make this change backwards compatible, switching to ordered locale data instead of keys might make this impossible)
Please note, that this might be due to some thoughts I have regarding the locale loading.
I consider always adding all locale data to the faker instance, but load the actual contents only lazily.
See also #830
Since that's somewhat in the future, I would like to take one step at a time and fix some of our limitations, regarding the use of multiple locales.
In this PR, I would like to mostly stick to existing API. We can replace/improve the existing API in a separate PR to make reviews easier and focused on a single change.
I'm not really happy of how big of a change this fix actually is and think about ways to simplify it (e.g. don't replace fallbackLocale with localeOrder and just make locale a single key or array).
@@ -8,8 +8,7 @@ import ar from '../locales/ar'; | |||
import en from '../locales/en'; | |||
|
|||
const faker = new Faker({ | |||
locale: 'ar', | |||
localeFallback: 'en', | |||
localeOrder: ['ar', 'en'], | |||
locales: { |
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.
If this would be an array and not object, you would not need the extra prop localeOrder
right?
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.
But, then you cannot easily switch between locales.
(strings are easier to replace, then full objects)
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.
See my comment above...
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.
How do you switch between locales?
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.
faker.localeOrder = ['fr'];
faker.setLocale('fr_BE');
const parts = locale.split('_'); | ||
for (let i = parts.length; i > 0; i--) { | ||
const subLocale = parts.slice(0, i).join('_'); |
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.
Side note:
ES6 way:
'en_AU_ocker'.split('_').reduce((result, _, i, parts) => {
return result.concat(parts.slice(0, parts.length - i).join('_'))
}, [])
No split:
'en_AU_ocker'.split('_').reduce((result, _, i, parts) => {
parts.pop()
return result.concat(parts.join('_'))
}, ['en_AU_ocker'])
locale?: UsableLocale; | ||
localeFallback?: UsableLocale; |
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 should somehow deprecate these with since v7 until v8 to make a smooth transition possible for users
The coverage gets fixed in #849. |
@pkuczynski: @Shinigami92 also wants to merge |
We will use a different approach. |
This PR introduces multiple fallback locales using an array of formats.
Old
New
I'm not sure about removing support for these to setters:
and the following constructor parameters
The coverage gets fixed in #849.