Skip to content

Commit 40b4380

Browse files
authored
fix: linked message fallbacking (#945)
1 parent 5f0c6cb commit 40b4380

File tree

6 files changed

+120
-10
lines changed

6 files changed

+120
-10
lines changed

packages/core-base/src/context.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export interface CoreOptions<
169169
messageCompiler?: MessageCompiler<Message>
170170
messageResolver?: MessageResolver
171171
localeFallbacker?: LocaleFallbacker
172+
fallbackContext?: CoreContext<Message, MessagesLocales, DateTimeFormatsLocales, NumberFormatsLocales>
172173
onWarn?: (msg: string, err?: Error) => void
173174
}
174175

@@ -228,7 +229,16 @@ export type CoreContext<
228229
> = CoreCommonContext<Message, Locales> &
229230
CoreTranslationContext<NonNullable<Messages>, Message> &
230231
CoreDateTimeContext<NonNullable<DateTimeFormats>> &
231-
CoreNumberContext<NonNullable<NumberFormats>>
232+
CoreNumberContext<NonNullable<NumberFormats>> & {
233+
fallbackContext?: CoreContext<
234+
Message,
235+
Messages,
236+
DateTimeFormats,
237+
NumberFormats,
238+
ResourceLocales,
239+
Locales
240+
>
241+
}
232242

233243
export interface CoreInternalContext {
234244
__datetimeFormatters: Map<string, Intl.DateTimeFormat>
@@ -312,6 +322,14 @@ export const setAdditionalMeta = /* #__PURE__*/ (
312322
export const getAdditionalMeta = /* #__PURE__*/ (): MetaInfo | null =>
313323
_additionalMeta
314324

325+
let _fallbackContext: CoreContext | null = null
326+
327+
export const setFallbackContext = (context: CoreContext | null): void => {
328+
_fallbackContext = context
329+
}
330+
331+
export const getFallbackContext = (): CoreContext | null => _fallbackContext
332+
315333
// ID for CoreContext
316334
let _cid = 0
317335

@@ -411,6 +429,9 @@ export function createCoreContext<Message = string>(options: any = {}): any {
411429
const localeFallbacker = isFunction(options.localeFallbacker)
412430
? options.localeFallbacker
413431
: _fallbacker || fallbackWithSimple
432+
const fallbackContext = isObject(options.fallbackContext)
433+
? options.fallbackContext
434+
: undefined
414435
const onWarn = isFunction(options.onWarn) ? options.onWarn : warn
415436

416437
// setup internal options
@@ -449,6 +470,7 @@ export function createCoreContext<Message = string>(options: any = {}): any {
449470
messageCompiler,
450471
messageResolver,
451472
localeFallbacker,
473+
fallbackContext,
452474
onWarn,
453475
__meta
454476
}

packages/core-base/src/runtime.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ export function createMessageContext<T = string, N = {}>(
178178
isNumber(options.pluralIndex) && normalizeNamed(pluralIndex, _named)
179179
const named = (key: string): unknown => _named[key]
180180

181-
// TODO: need to design resolve message function?
182181
function message(key: Path): MessageFunction<T> {
183182
// prettier-ignore
184183
const msg = isFunction(options.messages)
@@ -209,6 +208,11 @@ export function createMessageContext<T = string, N = {}>(
209208
? options.processor.interpolate
210209
: (DEFAULT_INTERPOLATE as unknown as MessageInterpolate<T>)
211210

211+
const linked = (key: Path, modifier?: string): MessageType<T> => {
212+
const msg = message(key)(ctx)
213+
return isString(modifier) ? _modifier(modifier)(msg as T) : msg
214+
}
215+
212216
const type =
213217
isPlainObject(options.processor) && isString(options.processor.type)
214218
? options.processor.type
@@ -218,11 +222,7 @@ export function createMessageContext<T = string, N = {}>(
218222
[HelperNameMap.LIST]: list,
219223
[HelperNameMap.NAMED]: named,
220224
[HelperNameMap.PLURAL]: plural,
221-
[HelperNameMap.LINKED]: (key: Path, modifier?: string): MessageType<T> => {
222-
// TODO: should check `key`
223-
const msg = message(key)(ctx)
224-
return isString(modifier) ? _modifier(modifier)(msg as T) : msg
225-
},
225+
[HelperNameMap.LINKED]: linked,
226226
[HelperNameMap.MESSAGE]: message,
227227
[HelperNameMap.TYPE]: type,
228228
[HelperNameMap.INTERPOLATE]: interpolate,

packages/core-base/src/translate.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -819,10 +819,32 @@ function getMessageContextOptions<Messages, Message = string>(
819819
message: LocaleMessageValue<Message>,
820820
options: TranslateOptions
821821
): MessageContextOptions<Message> {
822-
const { modifiers, pluralRules, messageResolver: resolveValue } = context
822+
const {
823+
modifiers,
824+
pluralRules,
825+
messageResolver: resolveValue,
826+
fallbackLocale,
827+
fallbackWarn,
828+
missingWarn,
829+
fallbackContext
830+
} = context
823831

824832
const resolveMessage = (key: string): MessageFunction<Message> => {
825-
const val = resolveValue(message, key)
833+
let val = resolveValue(message, key)
834+
835+
// fallback to root context
836+
if (val == null && fallbackContext) {
837+
const [, , message] = resolveMessageFormat(
838+
fallbackContext,
839+
key,
840+
locale,
841+
fallbackLocale as FallbackLocale,
842+
fallbackWarn,
843+
missingWarn
844+
)
845+
val = resolveValue(message, key)
846+
}
847+
826848
if (isString(val)) {
827849
let occurred = false
828850
const errorDetector = () => {

packages/core-base/test/translate.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,4 +792,22 @@ describe('edge cases', () => {
792792
})
793793
})
794794

795+
test('fallback context', () => {
796+
const parent = context({
797+
locale: 'en',
798+
messages: {
799+
en: { hello: 'hello man!', hi: 'hi' }
800+
}
801+
})
802+
803+
const ctx = context({
804+
locale: 'en',
805+
messages: {
806+
en: { hi: 'hi! @:hello' }
807+
}
808+
})
809+
ctx.fallbackContext = parent
810+
811+
expect(translate(ctx, 'hi')).toEqual('hi! hello man!')
812+
})
795813
/* eslint-enable @typescript-eslint/no-empty-function, @typescript-eslint/no-explicit-any */

packages/vue-i18n-core/src/composer.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
NOT_REOSLVED,
3232
MessageFunction,
3333
setAdditionalMeta,
34+
getFallbackContext,
35+
setFallbackContext,
3436
DEFAULT_LOCALE
3537
} from '@intlify/core-base'
3638
import { VueDevToolsTimelineEvents } from '@intlify/vue-devtools'
@@ -1913,6 +1915,8 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any {
19131915
let _context: CoreContext
19141916

19151917
function getCoreContext(): CoreContext {
1918+
_isGlobal && setFallbackContext(null)
1919+
19161920
const ctxOptions = {
19171921
version: VERSION,
19181922
locale: _locale.value,
@@ -1931,6 +1935,7 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any {
19311935
messageResolver: options.messageResolver,
19321936
__meta: { framework: 'vue' }
19331937
}
1938+
19341939
if (!__LITE__) {
19351940
;(ctxOptions as any).datetimeFormats = _datetimeFormats.value
19361941
;(ctxOptions as any).numberFormats = _numberFormats.value
@@ -1946,7 +1951,11 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any {
19461951
? (_context as unknown as CoreInternalContext).__v_emitter
19471952
: undefined
19481953
}
1949-
return createCoreContext(ctxOptions as any)
1954+
1955+
const ctx = createCoreContext(ctxOptions as any)
1956+
_isGlobal && setFallbackContext(ctx)
1957+
1958+
return ctx
19501959
}
19511960

19521961
_context = getCoreContext()
@@ -2057,9 +2066,17 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any {
20572066
if (__DEV__ || __FEATURE_PROD_INTLIFY_DEVTOOLS__) {
20582067
try {
20592068
setAdditionalMeta(getMetaInfo())
2069+
if (!_isGlobal) {
2070+
_context.fallbackContext = __root
2071+
? (getFallbackContext() as any)
2072+
: undefined
2073+
}
20602074
ret = fn(_context)
20612075
} finally {
20622076
setAdditionalMeta(null)
2077+
if (!_isGlobal) {
2078+
_context.fallbackContext = undefined
2079+
}
20632080
}
20642081
} else {
20652082
ret = fn(_context)

packages/vue-i18n-core/test/issues.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,34 @@ test('issue #854', async () => {
439439
`Fall back to translate 'hello' with root locale.`
440440
)
441441
})
442+
443+
test('issue #933', async () => {
444+
const i18n = createI18n({
445+
legacy: false,
446+
locale: 'en',
447+
fallbackLocale: 'en',
448+
messages: {
449+
en: {
450+
hello: 'hello man!'
451+
}
452+
}
453+
})
454+
455+
const App = defineComponent({
456+
setup() {
457+
const { t } = useI18n({
458+
messages: {
459+
en: {
460+
hi: 'hi! @:hello - @:local',
461+
local: 'local!'
462+
}
463+
}
464+
})
465+
return { t }
466+
},
467+
template: `<div>{{ t('hi') }}</div>`
468+
})
469+
const wrapper = await mount(App, i18n)
470+
471+
expect(wrapper.html()).toEqual('<div>hi! hello man! - local!</div>')
472+
})

0 commit comments

Comments
 (0)