Skip to content

Commit

Permalink
feat(i18n): better error localization
Browse files Browse the repository at this point in the history
  • Loading branch information
ayZagen committed Apr 5, 2024
1 parent 16dd6bf commit 7cf0b92
Show file tree
Hide file tree
Showing 37 changed files with 304 additions and 367 deletions.
4 changes: 2 additions & 2 deletions dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ const auth = new PlusAuthWidget('#pa__app', {
}, window['PlusAuth'])

setTimeout(() => {
auth.view.modeOptions.login.fields.username.value = 'test@test.com'
auth.view.modeOptions.login.fields = {
auth.view.modeOptions.login!.fields!.username!.value = 'test@test.com'
auth.view.modeOptions.login!.fields = {
username: null,
email: {
type: 'text',
Expand Down
10 changes: 5 additions & 5 deletions src/ui/components/GenericForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@
</template>

<script lang="ts">
import { computed, defineComponent, provide, reactive, ref } from 'vue';
import { computed, defineComponent, reactive, ref } from 'vue';
import type { AdditionalFields } from '../interfaces';
import type { FieldDefinition, ITranslatePath } from '../interfaces';
import type { PAlertProps } from './PAlert/PAlert';
Expand All @@ -135,14 +135,14 @@ export default defineComponent( {
default: () => false
},
fields: {
type: Object as () => AdditionalFields,
type: Object as () => Record<string, FieldDefinition>,
default: () => ({})
}
},
setup(props){
const formRef = ref<any>(null)
const alert = ref<boolean>(false)
const alertMsg = ref<string | null>(null)
const alertMsg = ref<ITranslatePath | null>(null)
const alertOptions = reactive<Record<string, any>>({})
const sortedFields = computed(()=> {
return Object.keys(props.fields)
Expand All @@ -159,7 +159,7 @@ export default defineComponent( {
* @param message Message to display in alert. Pass null or undefined to hide alert.
* @param options PAlert properties
*/
toggleAlert(message?: string | null, options?: Partial<PAlertProps>): void {
toggleAlert(message?: ITranslatePath, options?: Partial<PAlertProps>): void {
alert.value = false
if(!message){
alertMsg.value = null
Expand Down
3 changes: 2 additions & 1 deletion src/ui/components/PCheckBox/PCheckBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ export default defineComponent({
isPristine,
isFocused
})
const messages = computed(() => {
if (props.errorMessages?.length || !isPristine.value && errorMessages.value.length) {
if (!isPristine.value && errorMessages.value.length) {
return errorMessages.value
} else if (props.hint && (props.persistentHint || props.focused)) {
return props.hint
Expand Down
5 changes: 1 addition & 4 deletions src/ui/components/PTextField/PTextField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,7 @@ export default defineComponent({
})
const messages = computed(() => {
if (
props.errorMessages && (props.errorMessages as any[]).length
|| !isPristine.value && errorMessages.value.length
) {
if (!isPristine.value && errorMessages.value.length) {
return errorMessages.value || []
} else if (props.hint && (props.persistentHint || props.focused)) {
return props.hint || []
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/SocialConnectionButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</template>
<script setup lang="ts">
import { computed, inject } from 'vue';
import { inject } from 'vue';
import type { IWidgetSettings } from '../interfaces';
Expand Down
3 changes: 3 additions & 0 deletions src/ui/composables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './use_context'
export * from './use_locale'
export * from './use_http'
5 changes: 5 additions & 0 deletions src/ui/composables/use_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { inject } from 'vue';

import type { IPlusAuthContext } from '../interfaces';

export const useContext = () => inject('context') as IPlusAuthContext
5 changes: 5 additions & 0 deletions src/ui/composables/use_http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { inject } from 'vue';

import type { FetchWrapper } from '../utils/fetch';

export const useHttp = () => inject('http') as FetchWrapper
5 changes: 5 additions & 0 deletions src/ui/composables/use_locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { inject } from 'vue';

import { type Translator, translatorKey } from '../utils/translator';

export const useLocale = () => inject(translatorKey) as Translator
14 changes: 9 additions & 5 deletions src/ui/composables/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
import type { ITranslatePath } from '../interfaces';
import { getCurrentInstanceName, getUid } from '../utils/current_instance';
import type { EventProp } from '../utils/helpers';
import { wrapInArray } from '../utils/helpers';
import { isEmpty , wrapInArray } from '../utils/helpers';
import { propsFactory } from '../utils/props_factory';

import { makeFocusProps } from './focus';
Expand All @@ -39,7 +39,7 @@ type ValidateOnValue = 'blur' | 'input' | 'submit'
export interface ValidationProps {
disabled: boolean | null
error: boolean
errorMessages?: string | string[] | ITranslatePath[] | null
errorMessages?: string | string[] | ITranslatePath | ITranslatePath[] | null
focused: boolean
maxErrors: string | number
name: string | undefined
Expand All @@ -62,7 +62,11 @@ export const makeValidationProps = propsFactory({
persistentHint: Boolean,
messages: Array,
errorMessages: {
type: [Array, String, Object] as PropType<null | string | ITranslatePath[] | string[]>,
type: [
Array,
String,
Object
] as PropType<ValidationProps['errorMessages']>,
default: () => [],
},
maxErrors: {
Expand Down Expand Up @@ -103,7 +107,7 @@ export function useValidation(
const isDisabled = computed(() => !!(props.disabled ?? form?.isDisabled.value))
const isReadonly = computed(() => !!(props.readonly ?? form?.isReadonly.value))
const errorMessages = computed(() => {
return props.errorMessages?.length
return !isEmpty(props.errorMessages)
? wrapInArray(props.errorMessages)
.concat(internalErrorMessages.value)
.slice(0, Math.max(0, +props.maxErrors))
Expand All @@ -122,7 +126,7 @@ export function useValidation(
}
})
const isValid = computed(() => {
if (props.error || props.errorMessages?.length) return false
if (props.error || !isEmpty(props.errorMessages)) return false
if (!props.rules.length) return true
if (isPristine.value) {
return internalErrorMessages.value.length || validateOn.value.lazy ? null : true
Expand Down
2 changes: 1 addition & 1 deletion src/ui/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export type FieldDefinition = {
* The set value will be looked up from the locale dictionary
* or it can be set as object to provide arguments for the interpolation.
*/
errors?: null | string | string[] | ITranslatePath[] ;
errors?: null | string | string[] | ITranslatePath | ITranslatePath[] ;
/**
* Whether the field is required or not.
*/
Expand Down
31 changes: 20 additions & 11 deletions src/ui/utils/form_generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import { isEmail, isPhone } from '.';
export function useGenericForm(
name: WidgetModes,
defaultFields?: MaybeRef<AdditionalFields | null>,
action?: (fields: Record<string, any>, formFields: AdditionalFields) => Promise<any>
action?: (
fields: Record<string, any>,
formFields: Record<string, FieldDefinition>
) => Promise<any>
) {
const settings = inject('settings') as Partial<IWidgetSettings> || {}
const form = ref<typeof GenericForm>(null as any)
Expand All @@ -37,23 +40,18 @@ export function useGenericForm(
} : {} as any,
defaultFields || {}
)
const merged = computed<AdditionalFields>(() => {
const merged = computed<Record<string, FieldDefinition>>(() => {
const { fields } = settings.modeOptions?.[name] || {}
const merged = deepToRaw(deepmerge( defuFields, fields || {}, { clone: true }))
for (const field in merged) {
if (!merged[field]) {
delete merged[field]
}
}
// for (const field in fields) {
// if (!fields[field]) {
// delete fields[field]
// }
// }
return merged
return merged as any
})

const mergedFields = toReactive<AdditionalFields>(merged)
const mergedFields = toReactive<Record<string, FieldDefinition>>(merged)

return {
form,
Expand Down Expand Up @@ -118,14 +116,25 @@ export function useGenericForm(
await action?.(fieldsWithValues, mergedFields)
} catch (e) {
if (settings.modeOptions?.[name]?.responseErrorHandler) {
settings.modeOptions[name]!.responseErrorHandler.call(
settings.modeOptions[name]!.responseErrorHandler!.call(
undefined,
e,
formRef,
mergedFields
)
} else {
throw e
if (e.field && mergedFields[e.field]) {
mergedFields[e.field].errors = {
path: `errors.${e.error}`,
args: e,
}
} else {
form.value.toggleAlert({
path: `errors.${e.error}`,
args: e,
fallback: e.error_description || e.message || e.name || e
})
}
}
} finally {
loading.value = false
Expand Down
11 changes: 11 additions & 0 deletions src/ui/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,14 @@ export function wrapInArray<T>(
: Array.isArray(v)
? v as any : [v]
}


export function isEmpty(obj: any){
if(Array.isArray(obj) || typeof obj === 'string'){
return obj.length === 0
} else if( obj && typeof obj === 'object') {
return Object.keys(obj).length === 0
} else {
return true
}
}
2 changes: 1 addition & 1 deletion src/ui/utils/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Component, ComponentOptions } from 'vue';
import type { Component } from 'vue';

import Consent from '../views/Consent.vue';
import FillMissing from '../views/FillMissing.vue';
Expand Down
30 changes: 21 additions & 9 deletions src/ui/utils/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ export class Translator {
private fallBackLocale: string;
private dictionary: any;
private selectedLocale: Ref

constructor(dictionary: any, fallbackLocale?: string, selectedLocale?: string) {
this.dictionary = dictionary;
this.fallBackLocale = fallbackLocale || 'en'
this.selectedLocale = ref<string | undefined>(selectedLocale || this.fallBackLocale)
}

get localeRef() {
return this.selectedLocale
}
set locale(locale: string){

set locale(locale: string) {
this.selectedLocale.value = locale
}

get locale(): string {
return this.selectedLocale.value
}
Expand All @@ -32,17 +36,24 @@ export class Translator {
fallback?: string,
locale?: string
} = {}
){
) {
if (!opts.fallback && (params instanceof Error || typeof params === 'string')) {
opts.fallback = params['error_description']
|| params['error_details']
|| params.message
|| params.name
|| params
}
const locale = opts.locale || this.locale
const value = propertyAccessor(this.dictionary[locale], key)
const value = propertyAccessor(this.dictionary[locale], key)
|| propertyAccessor(this.dictionary[this.fallBackLocale], key);
if(value) {
if (value) {
return this._interpolate(
value,
params,
locale
)
} else if(opts.fallback){
} else if (opts.fallback) {
return this._interpolate(
propertyAccessor(this.dictionary[locale], opts.fallback)
|| propertyAccessor(this.dictionary[this.fallBackLocale], opts.fallback)
Expand All @@ -54,12 +65,13 @@ export class Translator {
return key
}
}
_interpolate(str: string, args: any, locale: string){
if(!str || !args){

_interpolate(str: string, args: any, locale: string) {
if (!str || !args) {
return str
}
const replace = (arg: any) => {
if(isObject(arg)){
if (isObject(arg)) {
const normalizedArg = keysToDotNotation(arg)
Object.keys(normalizedArg).forEach(key => {
const searchRegexp = new RegExp(`\\{\\s*${escapeRegExp(key)}\\s*\\}`, 'gm')
Expand All @@ -73,7 +85,7 @@ export class Translator {
})
}
}
if(Array.isArray(args)){
if (Array.isArray(args)) {
args.forEach(replace)
} else {
replace(args)
Expand Down
9 changes: 4 additions & 5 deletions src/ui/views/Consent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,16 @@
</template>

<script lang="ts">
import { defineComponent, inject } from 'vue';
import { defineComponent } from 'vue';
import type { IPlusAuthContext } from '../interfaces';
import { useContext, useHttp } from '../composables';
import { resolveClientLogo } from '../utils';
import type { FetchWrapper } from '../utils/fetch';
export default defineComponent({
name: 'Consent',
setup(){
const http = inject('http') as FetchWrapper
const context = inject('context') as IPlusAuthContext
const http = useHttp()
const context = useContext()
const _scopes = [...context.details.scopes?.new || []]
return {
Expand Down
Loading

0 comments on commit 7cf0b92

Please sign in to comment.