Skip to content

Commit 193a96f

Browse files
committedOct 19, 2024·
feat: add setFallbackLocale for i18n closes #4872
1 parent ecb540a commit 193a96f

File tree

3 files changed

+68
-7
lines changed

3 files changed

+68
-7
lines changed
 

‎.changeset/pink-poets-reflect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vee-validate/i18n": patch
3+
---
4+
5+
feat: add setFallbackLocale for i18n closes #4872

‎packages/i18n/src/index.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type RootI18nDictionary = Record<string, PartialI18nDictionary>;
1717

1818
class Dictionary {
1919
public locale: string;
20+
public fallbackLocale?: string;
2021

2122
private container: RootI18nDictionary;
2223
private interpolateOptions: InterpolateOptions;
@@ -33,7 +34,19 @@ class Dictionary {
3334
}
3435

3536
public resolve(ctx: FieldValidationMetaInfo, interpolateOptions?: InterpolateOptions) {
36-
return this.format(this.locale, ctx, interpolateOptions);
37+
let result = this.format(this.locale, ctx, interpolateOptions);
38+
if (!result && this.fallbackLocale && this.fallbackLocale !== this.locale) {
39+
result = this.format(this.fallbackLocale, ctx, interpolateOptions);
40+
}
41+
42+
return result || this.getDefaultMessage(this.locale, ctx);
43+
}
44+
45+
public getDefaultMessage(locale: string, ctx: FieldValidationMetaInfo) {
46+
const { label, name } = ctx;
47+
const fieldName = this.resolveLabel(locale, name, label);
48+
49+
return `${fieldName} is not valid`;
3750
}
3851

3952
public getLocaleDefault(locale: string, field: string): string | ValidationMessageGenerator | undefined {
@@ -54,7 +67,7 @@ class Dictionary {
5467
const fieldName = this.resolveLabel(locale, name, label);
5568

5669
if (!rule) {
57-
message = this.getLocaleDefault(locale, name) || `${fieldName} is not valid`;
70+
message = this.getLocaleDefault(locale, name) || '';
5871
return isCallable(message)
5972
? message(ctx)
6073
: interpolate(message, { ...form, field: fieldName }, interpolateOptions ?? this.interpolateOptions);
@@ -63,7 +76,7 @@ class Dictionary {
6376
// find if specific message for that field was specified.
6477
message = this.container[locale]?.fields?.[name]?.[rule.name] || this.container[locale]?.messages?.[rule.name];
6578
if (!message) {
66-
message = this.getLocaleDefault(locale, name) || `${fieldName} is not valid`;
79+
message = this.getLocaleDefault(locale, name) || '';
6780
}
6881

6982
return isCallable(message)
@@ -121,6 +134,13 @@ function setLocale(locale: string) {
121134
DICTIONARY.locale = locale;
122135
}
123136

137+
/**
138+
* Sets the fallback locale.
139+
*/
140+
function setFallbackLocale(locale: string) {
141+
DICTIONARY.fallbackLocale = locale;
142+
}
143+
124144
/**
125145
* Loads a locale file from URL and merges it with the current dictionary
126146
*/
@@ -143,4 +163,4 @@ async function loadLocaleFromURL(url: string) {
143163
}
144164
}
145165

146-
export { localize, setLocale, loadLocaleFromURL };
166+
export { localize, setLocale, loadLocaleFromURL, setFallbackLocale };

‎packages/i18n/tests/index.spec.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Ref } from 'vue';
22
import { defineRule, configure, useField } from '@/vee-validate';
33
import { required, between } from '@/rules';
4-
import { localize, setLocale } from '@/i18n';
4+
import { localize, setFallbackLocale, setLocale } from '@/i18n';
55
import { mountWithHoc, setValue, flushPromises } from '../../vee-validate/tests/helpers';
66

77
defineRule('required', required);
@@ -550,7 +550,7 @@ describe('interpolation preserves placeholders if not found', () => {
550550
});
551551
});
552552

553-
// begin - #4726 - custom interpolation options
553+
// #4726 - custom interpolation options
554554
test('custom interpolation options - interpolates object params with short format', async () => {
555555
configure({
556556
generateMessage: localize(
@@ -833,4 +833,40 @@ describe('interpolation preserves placeholders if not found', () => {
833833
expect(error.textContent).toContain('The name field must be between 0 and {{max}}');
834834
});
835835
});
836-
// end - #4726 - custom interpolation options tests
836+
837+
test('can define fallback locale', async () => {
838+
configure({
839+
generateMessage: localize({
840+
en: {
841+
messages: {
842+
test: `Field is required`,
843+
},
844+
},
845+
ar: {
846+
messages: {},
847+
},
848+
}),
849+
});
850+
851+
setLocale('ar');
852+
setFallbackLocale('en');
853+
defineRule('test', () => false);
854+
855+
const wrapper = mountWithHoc({
856+
template: `
857+
<div>
858+
<Field name="name" rules="test" v-slot="{ field, errors }">
859+
<input v-bind="field" type="text">
860+
<span id="error">{{ errors[0] }}</span>
861+
</Field>
862+
</div>
863+
`,
864+
});
865+
866+
const error = wrapper.$el.querySelector('#error');
867+
const input = wrapper.$el.querySelector('input');
868+
setValue(input, '12');
869+
await flushPromises();
870+
871+
expect(error.textContent).toContain('Field is required');
872+
});

0 commit comments

Comments
 (0)
Please sign in to comment.