Skip to content

Commit 4f01902

Browse files
authored
feat: global props and methods injection for composable mode (#122)
* feat: global props and methods injection for composable mode * udpate * add option
1 parent 632e1ea commit 4f01902

File tree

12 files changed

+202
-163
lines changed

12 files changed

+202
-163
lines changed

examples/composable/scope/global.html

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
<div id="app">
1111
<h1>Root</h1>
1212
<form>
13-
<label>{{ t('message.language') }}</label>
14-
<select v-model="locale">
13+
<label>{{ $t('message.language') }}</label>
14+
<select v-model="$i18n.locale">
1515
<option value="en">en</option>
1616
<option value="ja">ja</option>
1717
</select>
1818
</form>
19-
<p>{{ t('message.hello') }}</p>
19+
<p>{{ $t('message.hello') }}</p>
2020
<Child />
2121
</div>
2222
<script>
2323
const { createApp } = Vue
24-
const { createI18n, useI18n } = VueI18n
24+
const { createI18n } = VueI18n
2525

2626
const SlotChild = {
2727
template: `
@@ -34,18 +34,15 @@ <h1>Root</h1>
3434
<div class="sub-child">
3535
<h1>Sub Child</h1>
3636
<form>
37-
<label>{{ t('message.language') }}</label>
38-
<select v-model="locale">
37+
<label>{{ $t('message.language') }}</label>
38+
<select v-model="$i18n.locale">
3939
<option value="en">en</option>
4040
<option value="ja">ja</option>
4141
</select>
4242
</form>
43-
<p>{{ t('message.hi') }}</p>
43+
<p>{{ $t('message.hi') }}</p>
4444
</div>
45-
`,
46-
setup() {
47-
return useI18n()
48-
}
45+
`
4946
}
5047

5148
const Child = {
@@ -57,27 +54,24 @@ <h1>Sub Child</h1>
5754
<div class="child">
5855
<h1>Child</h1>
5956
<form>
60-
<label>{{ t('message.language') }}</label>
61-
<select v-model="locale">
57+
<label>{{ $t('message.language') }}</label>
58+
<select v-model="$i18n.locale">
6259
<option value="en">en</option>
6360
<option value="ja">ja</option>
6461
</select>
6562
</form>
66-
<p>{{ t('message.hi') }}</p>
63+
<p>{{ $t('message.hi') }}</p>
6764
<SubChild />
6865
t inside of slot
6966
<SlotChild>
70-
{{ t('message.hi') }}
67+
{{ $t('message.hi') }}
7168
</SlotChild>
7269
i18n-t inside of slot
7370
<SlotChild>
7471
<i18n-t keypath='message.hi'/>
7572
</SlotChild>
7673
</div>
77-
`,
78-
setup() {
79-
return useI18n()
80-
}
74+
`
8175
}
8276

8377
const i18n = createI18n({
@@ -101,10 +95,7 @@ <h1>Child</h1>
10195
})
10296

10397
const app = createApp({
104-
components: { Child },
105-
setup() {
106-
return useI18n()
107-
}
98+
components: { Child }
10899
})
109100
app.use(i18n)
110101
app.mount('#app')

examples/composable/scope/inherit-locale.html

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
<div id="app">
1111
<h1>Root</h1>
1212
<form>
13-
<label>{{ t('message.language') }}</label>
14-
<select v-model="locale">
13+
<label>{{ $t('message.language') }}</label>
14+
<select v-model="$i18n.locale">
1515
<option value="en">en</option>
1616
<option value="ja">ja</option>
1717
</select>
1818
</form>
19-
<p>{{ t("message.hello") }}</p>
19+
<p>{{ $t("message.hello") }}</p>
2020
<Child />
2121
</div>
2222
<script>
@@ -86,10 +86,7 @@ <h1>Child</h1>
8686
})
8787

8888
const app = createApp({
89-
components: { Child },
90-
setup() {
91-
return useI18n()
92-
}
89+
components: { Child }
9390
})
9491
app.use(i18n)
9592
app.mount('#app')

examples/composable/scope/local.html

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
<div id="app">
1111
<h1>Root</h1>
1212
<form>
13-
<label>{{ t('message.language') }}</label>
14-
<select v-model="locale">
13+
<label>{{ $t('message.language') }}</label>
14+
<select v-model="$i18n.locale">
1515
<option value="en">en</option>
1616
<option value="ja">ja</option>
1717
</select>
1818
</form>
19-
<p>{{ t("message.hello") }}</p>
19+
<p>{{ $t("message.hello") }}</p>
2020
<Child />
2121
</div>
2222
<script>
@@ -81,10 +81,7 @@ <h1>Child</h1>
8181
})
8282

8383
const app = createApp({
84-
components: { Child },
85-
setup() {
86-
return useI18n()
87-
}
84+
components: { Child }
8885
})
8986
app.use(i18n)
9087
app.mount('#app')

examples/composable/started.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
const app = createApp({
4343
setup() {
44+
// `useI18n` return the global compser that is created at `createI18n`
4445
return useI18n()
4546
}
4647
})

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
"benchmark": "yarn build && node ./benchmark/index.js",
9999
"build": "node ./scripts/build.js",
100100
"build:sourcemap": "yarn build --sourcemap",
101-
"build:type": "yarn build --types && tail -n +11 src/vue.d.ts >> ./dist/vue-i18n.d.ts",
101+
"build:type": "yarn build --types && tail -n +14 src/vue.d.ts >> ./dist/vue-i18n.d.ts",
102102
"build:watch": "tsc -p . --watch",
103103
"clean": "npm-run-all --parallel clean:*",
104104
"clean:cache": "yarn clean:cache:rollup && yarn clean:cache:jest",

src/directive.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type VTDirectiveValue = {
2323
function getComposer<Messages, DateTimeFormats, NumberFormats>(
2424
i18n: I18n<Messages, DateTimeFormats, NumberFormats>,
2525
instance: ComponentInternalInstance
26-
): Composer<Messages, DateTimeFormats, NumberFormats> | null {
26+
): Composer<Messages, DateTimeFormats, NumberFormats> {
2727
const i18nInternal = (i18n as unknown) as I18nInternal
2828
if (i18n.mode === 'composable') {
2929
return (i18nInternal.__getInstance<
@@ -66,10 +66,6 @@ export function vTDirective<Messages, DateTimeFormats, NumberFormats>(
6666
}
6767

6868
const composer = getComposer(i18n, instance.$)
69-
if (!composer) {
70-
throw createI18nError(I18nErrorCodes.NOT_FOUND_COMPOSER)
71-
}
72-
7369
if (__DEV__ && modifiers.preserve) {
7470
warn(getWarnMessage(I18nWarnCodes.NOT_SUPPORTED_PRESERVE))
7571
}

src/errors.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const enum I18nErrorCodes {
2020
// diretive module errors
2121
REQUIRED_VALUE,
2222
INVALID_VALUE,
23-
NOT_FOUND_COMPOSER,
2423
// for enhancement
2524
__EXTEND_POINT__
2625
}
@@ -43,6 +42,5 @@ export const errorMessages: { [code: number]: string } = {
4342
[I18nErrorCodes.UNEXPECTED_ERROR]: 'Unexpeced error',
4443
[I18nErrorCodes.NOT_AVAILABLE_IN_LEGACY_MODE]: 'Not available in legacy mode',
4544
[I18nErrorCodes.REQUIRED_VALUE]: `Required in value: {0}`,
46-
[I18nErrorCodes.INVALID_VALUE]: `Invalid value`,
47-
[I18nErrorCodes.NOT_FOUND_COMPOSER]: `Not found Composer`
45+
[I18nErrorCodes.INVALID_VALUE]: `Invalid value`
4846
}

src/i18n.ts

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {
66
getCurrentInstance,
77
ComponentInternalInstance,
88
ComponentOptions,
9-
App
9+
App,
10+
isRef
1011
} from 'vue'
11-
import { LocaleMessageDictionary } from './core/context'
12+
import { Locale, FallbackLocale, LocaleMessageDictionary } from './core/context'
1213
import { DateTimeFormat, NumberFormat } from './core/types'
1314
import {
1415
VueMessageType,
@@ -55,15 +56,21 @@ export type I18nOptions = I18nAdditionalOptions &
5556
*/
5657
export interface I18nAdditionalOptions {
5758
/**
58-
* Whether vue-i18n legacy API use on your Vue App.
59+
* Whether vue-i18n legacy API use on your Vue App
5960
*
6061
* @default false
6162
*/
6263
legacy?: boolean
64+
/**
65+
* Whether Whether to inject global props & methods into for each component
66+
*
67+
* @default true
68+
*/
69+
globalInjection?: boolean
6370
}
6471

6572
/**
66-
* I18n API mode
73+
* Vue I18n API mode
6774
*/
6875
export type I18nMode = 'legacy' | 'composable'
6976

@@ -72,7 +79,7 @@ export type I18nMode = 'legacy' | 'composable'
7279
*/
7380
export interface I18n<Messages = {}, DateTimeFormats = {}, NumberFormats = {}> {
7481
/**
75-
* I18n API mode
82+
* Vue I18n API mode
7683
*
7784
* @remarks
7885
* if you specified `legacy: true` option in `createI18n`, return `legacy`,
@@ -238,6 +245,7 @@ export function createI18n<
238245
Options['numberFormats']
239246
> {
240247
const __legacyMode = !!options.legacy
248+
const __globalInjection = !options.globalInjection
241249
const __instances = new Map<
242250
ComponentInternalInstance,
243251
VueI18n<Messages> | Composer<Messages>
@@ -264,6 +272,13 @@ export function createI18n<
264272
app.__VUE_I18N_SYMBOL__ = symbol
265273
app.provide(app.__VUE_I18N_SYMBOL__, i18n)
266274

275+
if (!__legacyMode && __globalInjection) {
276+
injectGlobalFields<Messages, DateTimeFormats, NumberFormats>(
277+
app,
278+
i18n.global
279+
)
280+
}
281+
267282
if (__FEATURE_FULL_INSTALL__) {
268283
apply<Messages, DateTimeFormats, NumberFormats>(app, i18n, ...options)
269284
}
@@ -545,3 +560,78 @@ function setupLifeCycle<Messages, DateTimeFormats, NumberFormats>(
545560
i18n.__deleteInstance(target)
546561
}, target)
547562
}
563+
564+
/**
565+
* Exported composer interface
566+
*
567+
* @remarks
568+
* This interface is the {@link I18n.global | global composer } that is provided interface that is injected into each component with `app.config.globalProperties`.
569+
*/
570+
export interface ExportedComposer {
571+
/**
572+
* Locale
573+
*
574+
* @remarks
575+
* This property is proxy-like property for `composer#locale`. About details, see the {@link Composer | Composer#locale } property
576+
*/
577+
locale: Locale
578+
/**
579+
* Fallback locale
580+
*
581+
* @remarks
582+
* This property is proxy-like property for `composer#fallbackLocale`. About details, see the {@link Composer | Composer#fallbackLocale } property
583+
*/
584+
fallbackLocale: FallbackLocale
585+
/**
586+
* Available locales
587+
*
588+
* @remarks
589+
* This property is proxy-like property for `composer#availableLocales`. About details, see the {@link Composer | Composer#availableLocales } property
590+
*/
591+
readonly availableLocales: Locale[]
592+
}
593+
594+
const globalExportProps = [
595+
'locale',
596+
'fallbackLocale',
597+
'availableLocales'
598+
] as const
599+
const globalExportMethods = ['t', 'd', 'n', 'tm'] as const
600+
601+
function injectGlobalFields<Messages, DateTimeFormats, NumberFormats>(
602+
app: App,
603+
composer: Composer<Messages, DateTimeFormats, NumberFormats>
604+
): void {
605+
const i18n = Object.create(null)
606+
globalExportProps.forEach(prop => {
607+
const desc = Object.getOwnPropertyDescriptor(composer, prop)
608+
if (!desc) {
609+
throw createI18nError(I18nErrorCodes.UNEXPECTED_ERROR)
610+
}
611+
const wrap = isRef(desc.value) // check computed props
612+
? {
613+
get() {
614+
return desc.value.value
615+
},
616+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
617+
set(val: any) {
618+
desc.value.value = val
619+
}
620+
}
621+
: {
622+
get() {
623+
return desc.get && desc.get()
624+
}
625+
}
626+
Object.defineProperty(i18n, prop, wrap)
627+
})
628+
app.config.globalProperties.$i18n = i18n
629+
630+
globalExportMethods.forEach(method => {
631+
const desc = Object.getOwnPropertyDescriptor(composer, method)
632+
if (!desc) {
633+
throw createI18nError(I18nErrorCodes.UNEXPECTED_ERROR)
634+
}
635+
Object.defineProperty(app.config.globalProperties, `$${method}`, desc)
636+
})
637+
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export {
4545
I18nMode,
4646
I18nScope,
4747
ComposerAdditionalOptions,
48-
UseI18nOptions
48+
UseI18nOptions,
49+
ExportedComposer
4950
} from './i18n'
5051
export {
5152
Translation,

0 commit comments

Comments
 (0)