Skip to content

Commit

Permalink
Merge pull request #2972 from n8n-io/um/i18n-nds
Browse files Browse the repository at this point in the history
support i18n in nds
  • Loading branch information
mutdmour committed Mar 11, 2022
2 parents 4fcb86d + d628f81 commit 5bc8f90
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 69 deletions.
14 changes: 11 additions & 3 deletions packages/design-system/src/components/N8nFormInput/FormInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ import N8nInputLabel from '../N8nInputLabel';
import { getValidationError, VALIDATORS } from './validators';
import { Rule, RuleGroup, IValidator } from "../../../../editor-ui/src/Interface";
export default Vue.extend({
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
export default mixins(Locale).extend({
name: 'n8n-form-input',
components: {
N8nInput,
Expand Down Expand Up @@ -133,7 +136,12 @@ export default Vue.extend({
},
computed: {
validationError(): string | null {
return this.getValidationError();
const error = this.getValidationError();
if (error) {
return this.t(error.messageKey, error.options);
}
return null;
},
hasDefaultSlot(): boolean {
return !!this.$slots.default;
Expand All @@ -146,7 +154,7 @@ export default Vue.extend({
},
},
methods: {
getValidationError(): string | null {
getValidationError(): ReturnType<IValidator['validate']> {
const rules = (this.validationRules || []) as (Rule | RuleGroup)[];
const validators = {
...VALIDATORS,
Expand Down
92 changes: 61 additions & 31 deletions packages/design-system/src/components/N8nFormInput/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,85 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
REQUIRED: {
validate: (value: string | number | boolean | null | undefined) => {
if (typeof value === 'string' && !!value.trim()) {
return;
return false;
}

if (typeof value === 'number' || typeof value === 'boolean') {
return;
return false;
}
throw new Error('This field is required');

return {
messageKey: 'formInput.validator.fieldRequired',
};
},
},
MIN_LENGTH: {
validate: (value: string, config: { minimum: number }) => {
if (value.length < config.minimum) {
throw new Error(`Must be at least ${config.minimum} characters`);
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
if (typeof value === 'string' && value.length < config.minimum) {
return {
messageKey: 'formInput.validator.minCharactersRequired',
options: config,
};
}

return false;
},
},
MAX_LENGTH: {
validate: (value: string, config: { maximum: number }) => {
if (value.length > config.maximum) {
throw new Error(`Must be at most ${config.maximum} characters`);
validate: (value: string | number | boolean | null | undefined, config: { maximum: number }) => {
if (typeof value === 'string' && value.length > config.maximum) {
return {
messageKey: 'formInput.validator.maxCharactersRequired',
options: config,
};
}

return false;
},
},
CONTAINS_NUMBER: {
validate: (value: string, config: { minimum: number }) => {
const numberCount = (value.match(/\d/g) || []).length;
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
}

const numberCount = (value.match(/\d/g) || []).length;
if (numberCount < config.minimum) {
throw new Error(`Must have at least ${config.minimum} number${config.minimum > 1 ? 's' : ''}`);
return {
messageKey: 'formInput.validator.numbersRequired',
options: config,
};
}

return false;
},
},
VALID_EMAIL: {
validate: (value: string) => {
validate: (value: string | number | boolean | null | undefined) => {
if (!emailRegex.test(String(value).trim().toLowerCase())) {
throw new Error('Must be a valid email');
return {
messageKey: 'formInput.validator.validEmailRequired',
};
}

return false;
},
},
CONTAINS_UPPERCASE: {
validate: (value: string, config: { minimum: number }) => {
const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
validate: (value: string | number | boolean | null | undefined, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
}

const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
if (uppercaseCount < config.minimum) {
throw new Error(`Must have at least ${config.minimum} uppercase character${
config.minimum > 1 ? 's' : ''
}`);
return {
messageKey: 'formInput.validator.uppercaseCharsRequired',
options: config,
};
}

return false;
},
},
DEFAULT_PASSWORD_RULES: {
Expand All @@ -66,7 +97,9 @@ export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
{ name: 'CONTAINS_NUMBER', config: { minimum: 1 } },
{ name: 'CONTAINS_UPPERCASE', config: { minimum: 1 } },
],
defaultError: '8+ characters, at least 1 number and 1 capital letter',
defaultError: {
messageKey: 'formInput.validator.defaultPasswordRequirements',
},
},
{ name: 'MAX_LENGTH', config: {maximum: 64} },
],
Expand All @@ -78,7 +111,7 @@ export const getValidationError = (
validators: { [key: string]: IValidator | RuleGroup },
validator: IValidator | RuleGroup,
config?: any, // tslint:disable-line:no-any
): string | null => {
): ReturnType<IValidator['validate']> => {
if (validator.hasOwnProperty('rules')) {
const rules = (validator as RuleGroup).rules;
for (let i = 0; i < rules.length; i++) {
Expand Down Expand Up @@ -107,22 +140,19 @@ export const getValidationError = (
validators[rule.name] as IValidator,
rule.config,
);
if (error) {
return (validator as RuleGroup).defaultError || error;
if (error && (validator as RuleGroup).defaultError !== undefined) {
// @ts-ignore
return validator.defaultError;
} else if (error) {
return error;
}
}
}
} else if (
validator.hasOwnProperty('validate')
) {
try {
(validator as IValidator).validate(value, config);
} catch (e: unknown) {
if (e instanceof Error) {
return e.message;
}
}
return (validator as IValidator).validate(value, config);
}

return null;
return false;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</div>
<div v-else :class="$style.infoContainer">
<div>
<n8n-text :bold="true">{{firstName}} {{lastName}} {{isCurrentUser ? '(you)' : ''}}</n8n-text>
<n8n-text :bold="true">{{firstName}} {{lastName}} {{isCurrentUser ? this.t('nds.userInfo.you') : ''}}</n8n-text>
</div>
<div>
<n8n-text size="small" color="text-light">{{email}}</n8n-text>
Expand All @@ -25,8 +25,10 @@ import Vue from 'vue';
import N8nText from '../N8nText';
import N8nAvatar from '../N8nAvatar';
import N8nBadge from '../N8nBadge';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
export default Vue.extend({
export default mixins(Locale).extend({
name: 'n8n-users-info',
components: {
N8nAvatar,
Expand Down
21 changes: 5 additions & 16 deletions packages/design-system/src/components/N8nUserSelect/UserSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
:value="value"
:filterable="true"
:filterMethod="setFilter"
:placeholder="placeholder"
:placeholder="t('nds.userSelect.selectUser')"
:default-first-option="true"
:popper-append-to-body="true"
:popper-class="$style.limitPopperWidth"
:noMatchText="noMatchText"
:noDataText="noDataText"
:noDataText="t('nds.userSelect.noMatchingUsers')"
@change="onChange"
@blur="onBlur"
@focus="onFocus"
Expand All @@ -31,8 +30,10 @@ import N8nUserInfo from '../N8nUserInfo';
import { IUser } from '../../Interface';
import ElSelect from 'element-ui/lib/select';
import ElOption from 'element-ui/lib/option';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
export default Vue.extend({
export default mixins(Locale).extend({
name: 'n8n-user-select',
components: {
N8nUserInfo,
Expand All @@ -57,18 +58,6 @@ export default Vue.extend({
},
validator: (ids: string[]) => !ids.find((id) => typeof id !== 'string'),
},
placeholder: {
type: String,
default: 'Select user',
},
noMatchText: {
type: String,
default: 'No matches found',
},
noDataText: {
type: String,
default: 'No users',
},
currentUserId: {
type: String,
},
Expand Down
10 changes: 6 additions & 4 deletions packages/design-system/src/components/N8nUsersList/UsersList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
>
<n8n-user-info v-bind="user" :isCurrentUser="currentUserId === user.id" />
<div :class="$style.badgeContainer">
<n8n-badge v-if="user.isOwner" theme="secondary">Owner</n8n-badge>
<n8n-badge v-if="user.isOwner" theme="secondary">{{ t('nds.auth.roles.owner') }}</n8n-badge>
<n8n-action-toggle
v-if="!user.isOwner"
placement="bottom"
Expand All @@ -28,8 +28,10 @@ import N8nIcon from '../N8nIcon';
import N8nLink from '../N8nLink';
import N8nText from '../N8nText';
import N8nUserInfo from '../N8nUserInfo';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
export default Vue.extend({
export default mixins(Locale).extend({
name: 'n8n-users-list',
components: {
N8nActionToggle,
Expand Down Expand Up @@ -86,12 +88,12 @@ export default Vue.extend({
methods: {
getActions(user: IUser) {
const DELETE = {
label: 'Delete user',
label: this.t('nds.usersList.deleteUser'),
value: 'delete',
};
const REINVITE = {
label: 'Resend invite',
label: this.t('nds.usersList.reinviteUser'),
value: 'reinvite',
};
Expand Down
6 changes: 3 additions & 3 deletions packages/design-system/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ import N8nTooltip from './N8nTooltip';
import N8nUsersList from './N8nUsersList';
import N8nUserSelect from './N8nUserSelect';

import lang from 'element-ui/lib/locale/lang/en';
import locale from 'element-ui/lib/locale';
locale.use(lang);
import locale from '../locale';

export {
N8nActionBox,
Expand Down Expand Up @@ -130,4 +128,6 @@ export {
Message,
Notification,
CollapseTransition,

locale,
};
56 changes: 56 additions & 0 deletions packages/design-system/src/locale/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const hasOwnProperty = Object.prototype.hasOwnProperty;

export function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
};


const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
/**
* String format template
* - Inspired:
* https://github.com/ElemeFE/element/blob/dev/src/locale/format.js
* https://github.com/Matt-Esch/string-template/index.js
*/
export default function(Vue) {

/**
* template
*
* @param {String | Function} string
* @param {Array} ...args
* @return {String}
*/

function template(value, ...args) {
if (typeof value === 'function') {
return value(args);
}
const string = value;
if (args.length === 1 && typeof args[0] === 'object') {
args = args[0];
}

if (!args || !args.hasOwnProperty) {
args = {};
}

return string.replace(RE_NARGS, (match, prefix, i, index) => {
let result;

if (string[index - 1] === '{' &&
string[index + match.length] === '}') {
return i;
} else {
result = hasOwn(args, i) ? args[i] : null;
if (result === null || result === undefined) {
return '';
}

return result;
}
});
}

return template;
}
Loading

0 comments on commit 5bc8f90

Please sign in to comment.