From 7583485671835ecc3329519622948c1ce566f8fe Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Sun, 14 Oct 2018 03:09:22 +0900 Subject: [PATCH] :bug: bug(index): Add warning for circular reference in linked message (#438) by @exoego * Add warning for circular reference in linked message. * Allow non-ascii chars including numbers. * Circular reference warnings should tell us chain of reference. --- src/index.js | 24 ++++++++++++++++++------ test/unit/fixture/index.js | 4 ++++ test/unit/issues.test.js | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 2e27202ac..dcebb5ddc 100644 --- a/src/index.js +++ b/src/index.js @@ -199,7 +199,8 @@ export default class VueI18n { key: Path, host: any, interpolateMode: string, - values: any + values: any, + visitedLinkStack: Array ): any { if (!message) { return null } @@ -234,7 +235,7 @@ export default class VueI18n { // Check for the existance of links within the translated string if (ret.indexOf('@:') >= 0) { - ret = this._link(locale, message, ret, host, interpolateMode, values) + ret = this._link(locale, message, ret, host, interpolateMode, values, visitedLinkStack) } return this._render(ret, interpolateMode, values) @@ -246,7 +247,8 @@ export default class VueI18n { str: string, host: any, interpolateMode: string, - values: any + values: any, + visitedLinkStack: Array ): any { let ret: string = str @@ -263,11 +265,19 @@ export default class VueI18n { const link: string = matches[idx] // Remove the leading @: and the brackets const linkPlaceholder: string = link.substr(2).replace(bracketsMatcher, '') + + if (visitedLinkStack.includes(linkPlaceholder)) { + warn(`Circular reference found. "${link}" is already visited in the chain of ${visitedLinkStack.reverse().join(' <- ')}`) + return ret + } + visitedLinkStack.push(linkPlaceholder) + // Translate the link let translated: any = this._interpolate( locale, message, linkPlaceholder, host, interpolateMode === 'raw' ? 'string' : interpolateMode, - interpolateMode === 'raw' ? undefined : values + interpolateMode === 'raw' ? undefined : values, + visitedLinkStack ) if (this._isFallbackRoot(translated)) { @@ -287,6 +297,8 @@ export default class VueI18n { Array.isArray(values) ? values : [values] ) + visitedLinkStack.pop() + // Replace the link with the translated ret = !translated ? ret : ret.replace(link, translated) } @@ -311,10 +323,10 @@ export default class VueI18n { args: any ): any { let res: any = - this._interpolate(locale, messages[locale], key, host, interpolateMode, args) + this._interpolate(locale, messages[locale], key, host, interpolateMode, args, [key]) if (!isNull(res)) { return res } - res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args) + res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args, [key]) if (!isNull(res)) { if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) { warn(`Fall back to translate the keypath '${key}' with '${fallback}' locale.`) diff --git a/test/unit/fixture/index.js b/test/unit/fixture/index.js index b456f394b..d8cfc995a 100644 --- a/test/unit/fixture/index.js +++ b/test/unit/fixture/index.js @@ -17,6 +17,10 @@ export default { linkHyphen: '@:hyphen-hello', linkUnderscore: '@:underscore_hello', linkList: '@:message.hello: {0} {1}', + circular1: 'Foo @:message.circular2', + circular2: 'Bar @:message.circular3', + circular3: 'Buz @:message.circular1', + linkTwice: '@:message.hello: @:message.hello', 'hyphen-locale': 'hello hyphen', '1234': 'Number-based keys are found', '1mixedKey': 'Mixed keys are not found.' diff --git a/test/unit/issues.test.js b/test/unit/issues.test.js index 5719e296e..aa1b8f3af 100644 --- a/test/unit/issues.test.js +++ b/test/unit/issues.test.js @@ -300,6 +300,24 @@ describe('issues', () => { }) }) + describe('#247', () => { + it('should be warned if circular reference in linked locale message', () => { + const spy = sinon.spy(console, 'warn') + assert.strictEqual(vm.$i18n.t('message.circular1'), 'Foo Bar Buz @:message.circular1') + assert(spy.notCalled === false) + assert(spy.callCount === 1) + spy.restore() + }) + + it('should not be warned if same non-circular link used repeatedly', () => { + const spy = sinon.spy(console, 'warn') + assert.strictEqual(vm.$i18n.t('message.linkTwice'), 'the world: the world') + assert(spy.notCalled === true) + assert(spy.callCount === 0) + spy.restore() + }) + }) + describe('#377', () => { it('should be destroyed', done => { const el = document.createElement('div')