Skip to content

Commit

Permalink
⚡ improvement(index): silence fallback warnings (#510) by @SzNagyMisu
Browse files Browse the repository at this point in the history
* ⚡ improvement(option): silentFallbackWarn (#139)

* feature(option): add silentFallbackWarn to VueI18n constructor
  * silence fallback warnings
  * warn only if no translation is found at all

* adding typescript property declaration

* 📝 docs(options): document silentFallbackWarn

* Update vuepress/api/README.md

Co-Authored-By: SzNagyMisu <[email protected]>

* ⚡ improvement(option): silentFallbackWarn

* include case when pathRet is not null, undefined, array, plain object or string
* provide test case
  • Loading branch information
SzNagyMisu authored and kazupon committed Jan 23, 2019
1 parent e879024 commit ddc0c79
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 19 deletions.
19 changes: 15 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default class VueI18n {
_watcher: any
_i18nWatcher: Function
_silentTranslationWarn: boolean
_silentFallbackWarn: boolean
_dateTimeFormatters: Object
_numberFormatters: Object
_path: I18nPath
Expand Down Expand Up @@ -89,6 +90,9 @@ export default class VueI18n {
this._silentTranslationWarn = options.silentTranslationWarn === undefined
? false
: !!options.silentTranslationWarn
this._silentFallbackWarn = options.silentFallbackWarn === undefined
? false
: !!options.silentFallbackWarn
this._dateTimeFormatters = {}
this._numberFormatters = {}
this._path = new I18nPath()
Expand Down Expand Up @@ -184,6 +188,9 @@ export default class VueI18n {
get silentTranslationWarn (): boolean { return this._silentTranslationWarn }
set silentTranslationWarn (silent: boolean): void { this._silentTranslationWarn = silent }

get silentFallbackWarn (): boolean { return this._silentFallbackWarn }
set silentFallbackWarn (silent: boolean): void { this._silentFallbackWarn = silent }

_getMessages (): LocaleMessages { return this._vm.messages }
_getDateTimeFormats (): DateTimeFormats { return this._vm.dateTimeFormats }
_getNumberFormats (): NumberFormats { return this._vm.numberFormats }
Expand All @@ -210,6 +217,10 @@ export default class VueI18n {
return !val && !isNull(this._root) && this._fallbackRoot
}

_isSilentFallback (locale: Locale): boolean {
return this._silentFallbackWarn && (this._isFallbackRoot() || locale !== this.fallbackLocale)
}

_interpolate (
locale: Locale,
message: LocaleMessageObject,
Expand All @@ -230,7 +241,7 @@ export default class VueI18n {
if (isPlainObject(message)) {
ret = message[key]
if (typeof ret !== 'string') {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._isSilentFallback(locale)) {
warn(`Value of key '${key}' is not a string!`)
}
return null
Expand All @@ -243,7 +254,7 @@ export default class VueI18n {
if (typeof pathRet === 'string') {
ret = pathRet
} else {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._isSilentFallback(locale)) {
warn(`Value of key '${key}' is not a string!`)
}
return null
Expand Down Expand Up @@ -359,7 +370,7 @@ export default class VueI18n {

res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args, [key])
if (!isNull(res)) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._silentFallbackWarn) {
warn(`Fall back to translate the keypath '${key}' with '${fallback}' locale.`)
}
return res
Expand All @@ -379,7 +390,7 @@ export default class VueI18n {
host, 'string', parsedArgs.params
)
if (this._isFallbackRoot(ret)) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._silentFallbackWarn) {
warn(`Fall back to translate the keypath '${key}' with root locale.`)
}
/* istanbul ignore if */
Expand Down
1 change: 1 addition & 0 deletions src/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default {
options.i18n.formatter = this.$root.$i18n.formatter
options.i18n.fallbackLocale = this.$root.$i18n.fallbackLocale
options.i18n.silentTranslationWarn = this.$root.$i18n.silentTranslationWarn
options.i18n.silentFallbackWarn = this.$root.$i18n.silentFallbackWarn
options.i18n.pluralizationRules = this.$root.$i18n.pluralizationRules
options.i18n.preserveDirectiveContent = this.$root.$i18n.preserveDirectiveContent
}
Expand Down
173 changes: 158 additions & 15 deletions test/unit/silent.test.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,168 @@
describe('silent', () => {
it('should be suppressed translate warnings', () => {
const vm = new Vue({
i18n: new VueI18n({
locale: 'en',
silentTranslationWarn: true,
let spy
beforeEach(() => {
spy = sinon.spy(console, 'warn')
})
afterEach(() => {
spy.restore()
})

describe('silentTranslationWarn', () => {
it('should be suppressed translate warnings', () => {
const vm = new Vue({
i18n: new VueI18n({
locale: 'en',
silentTranslationWarn: true,
messages: {
en: { who: 'root' },
ja: { who: 'ルート' }
}
})
})

vm.$t('foo.bar.buz')
assert(spy.notCalled === true)

// change
vm.$i18n.silentTranslationWarn = false
vm.$t('foo.bar.buz')
assert(spy.callCount === 2)
})
})

describe('silentFallbackWarn', () => {
let i18n
beforeEach(() => {
i18n = new VueI18n({
locale: 'hu',
fallbackLocale: 'en',
silentFallbackWarn: true,
messages: {
en: { who: 'root' },
ja: { who: 'ルート' }
en: { winner: 'winner' },
hu: { chickenDinner: 'csirkevacsora' }
}
})
})

const spy = sinon.spy(console, 'warn')
vm.$t('foo.bar.buz')
assert(spy.notCalled === true)
it('should suppress `Fall back to ${fallback} locale` warnings', () => {
const vm = new Vue({ i18n })
const warningRegex = /Fall back to .* 'en' locale./
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

// change
vm.$i18n.silentTranslationWarn = false
vm.$t('foo.bar.buz')
assert(spy.callCount === 2)
vm.$i18n.silentFallbackWarn = false
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})

spy.restore()
it('should suppress `Fall back to root locale` warnings.', () => {
const el = document.createElement('div')
const root = new Vue({
i18n,
components: {
subComponent: {
i18n: { messages: { hu: { name: 'Név' } } },
render (h) { return h('p') }
}
},
render (h) { return h('sub-component') }
}).$mount(el)
const vm = root.$children[0]
const warningRegex = /Fall back to .* root locale./

vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

vm.$i18n.silentFallbackWarn = false
vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})

describe('if first try is null or undefined,', () => {
it('should suppress `not a string` warnings for fallback to fallbackLocale.', () => {
const vm = new Vue({ i18n })
const warningRegex = /Value of .* is not a string./
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

vm.$i18n.silentFallbackWarn = false
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})

it('should supress `not a string` warnings for fallback to root.', () => {
const el = document.createElement('div')
const root = new Vue({
i18n,
components: {
subComponent: {
i18n: { messages: { hu: { name: 'Név' } } },
render (h) { return h('p') }
}
},
render (h) { return h('sub-component') }
}).$mount(el)
const vm = root.$children[0]
const warningRegex = /Value of .* is not a string./
vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

vm.$i18n.silentFallbackWarn = false
vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})
})

describe('if first try is not null, undefined, array, plain object or string,', () => {
it('should suppress `not a string` warnings for fallback to fallbackLocale.', () => {
const vm = new Vue({
i18n: new VueI18n({
locale: 'hu',
fallbackLocale: 'en',
silentFallbackWarn: true,
messages: {
en: { winner: 'winner' },
hu: { winner: true } // translation value is boolean
}
})
})
const warningRegex = /Value of .* is not a string./
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

vm.$i18n.silentFallbackWarn = false
vm.$t('winner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})

it('should supress `not a string` warnings for fallback to root.', () => {
const el = document.createElement('div')
const root = new Vue({
i18n,
components: {
subComponent: {
i18n: { messages: { hu: { chickenDinner: 11 } } }, // translation value is number
render (h) { return h('p') }
}
},
render (h) { return h('sub-component') }
}).$mount(el)
const vm = root.$children[0]
const warningRegex = /Value of .* is not a string./
vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === false)

vm.$i18n.silentFallbackWarn = false
vm.$t('chickenDinner')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})
})

it('should not suppress `not a string` warnings when no further fallback is possible.', () => {
const vm = new Vue({ i18n })
const warningRegex = /Value of .* is not a string./
vm.$t('loser')
assert(spy.getCalls().some(call => call.args[0].match(warningRegex)) === true)
})
})
})
11 changes: 11 additions & 0 deletions vuepress/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ Whether suppress warnings outputted when localization fails.

If `true`, suppress localization fail warnings.

#### silentFallbackWarn

> :new: 8.8+
* **Type:** `Boolean`
* **Default:** `false`

Whether suppress warnings when falling back to either `fallbackLocale` or `root`.

If `true`, warnings will be generated only when no translation is available at all, and not for fallbacks.

#### preserveDirectiveContent

> 8.7+
Expand Down
9 changes: 9 additions & 0 deletions vuepress/guide/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ Outputs the following:

As in the example above, if the component doesn't have the locale message, it falls back to globally defined localization info. The component uses the language set in the root instance (in the above example: `locale: 'ja'`).

Note, that by default falling back to root locale generates two warnings in the console:

```console
[vue-i18n] Value of key 'message.greeting' is not a string!
[vue-i18n] Fall back to translate the keypath 'message.greeting' with root locale.
```

To suppress these warnings (while keeping those which warn of the total absence of translation for the given key) set `silentFallbackWarn: true` when initializing the `VueI18n` instance.

If you hope localize in the component locale, you can realize with `sync: false` and `locale` in `i18n` option.

## Translation in functional component
Expand Down
9 changes: 9 additions & 0 deletions vuepress/guide/fallback.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@ Output the below:
```html
<p>hello world</p>
```

Note, that by default falling back to `fallbackLocale` generates two console warnings:

```console
[vue-i18n] Value of key 'message' is not a string!
[vue-i18n] Fall back to translate the keypath 'message' with 'en' locale.
```

To suppress these warnings (while keeping those which warn of the total absence of translation for the given key) set `silentFallbackWarn: true` when initializing the `VueI18n` instance.

0 comments on commit ddc0c79

Please sign in to comment.