⚡ i18n feedback refactorings (#2597)
* ⚡ Create endpoint for node credential translation * ⚡ Add API helper method in FE * 🔨 Add creds JSON files to tsconfig * ⚡ Refactor credentials loading * ⚡ Refactor calls in CredentialConfig * ✏️ Add dummy translations * ⚡ Split translations per node * 🔥 Remove deprecated method * ⚡ Refactor nesting in collections * 🚚 Rename topParameter methods for accuracy * ✏️ Fill out GitHub dummy cred * 🚚 Clarify naming for collection utils * ✏️ Fill out dummy translation * 🔥 Remove surplus colons * 🔥 Remove logging * ⚡ Restore missing space * 🔥 Remove lingering colon * ⚡ Add path to InputLabel calls * ✏️ Fill out dummy translations * 🐛 Fix multipleValuesButtonText logic * ⚡ Add sample properties to be deleted * ⚡ Render deeply nested params * 📦 Update package-lock.json * 🔥 remove logging * ✏️ Add dummy value to Slack translation * ✏️ Add placeholder to dummy translation * ⚡ Fix placeholder rendering for button text * 👕 Fix lint * 🔥 Remove outdated comment * 🐛 Pass in missing arg for placeholder * ✏️ Fill out Slack translation * ⚡ Add explanatory comment * ✏️ Fill out dummy translation * ✏️ Update documentation * 🔥 Remove broken link * ✏️ Add pending functionality * ✏️ Fix indentation * 🐛 Fix method call in CredentialEdit * ⚡ Implement eventTriggerDescription * 🐛 Fix table-json-binary radio buttons * ✏️ Clarify usage of eventTriggerDescription * 🔥 Remove unneeded arg * 🐛 Fix display in CodeEdit and TextEdit * 🔥 Remove logging * ✏️ Add translation for test cred options * ✏️ Add test for separate file in same dir * ✏️ Add test for versioned node * ✏️ Add test for node in grouped dir * ✏️ Add minor clarifications * ✏️ Add nested collection test * ✏️ Add pending functionality * ⚡ Generalize collections handling * 🚚 Rename helper to remove redundancy * 🚚 Improve naming in helpers * ✏️ Improve helpers documentation * ✏️ Improve i18n methods documentation * 🚚 Make endpoint naming consistent * ✏️ Add final newlines * ✏️ Clean up JSON examples * ⚡ Reuse i18n method * ⚡ Improve utils readability * ⚡ Return early if cred translation exists * 🔥 Remove dummy translations
This commit is contained in:
@@ -4,14 +4,17 @@ import VueI18n from 'vue-i18n';
|
||||
import { Store } from "vuex";
|
||||
import Vue from 'vue';
|
||||
import { INodeTranslationHeaders, IRootState } from '@/Interface';
|
||||
import {
|
||||
deriveMiddleKey,
|
||||
isNestedInCollectionLike,
|
||||
normalize,
|
||||
insertOptionsAndValues,
|
||||
} from "./utils";
|
||||
|
||||
const englishBaseText = require('./locales/en');
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
||||
const REUSABLE_DYNAMIC_TEXT_KEY = 'reusableDynamicText';
|
||||
const CREDENTIALS_MODAL_KEY = 'credentialsModal';
|
||||
const NODE_VIEW_KEY = 'nodeView';
|
||||
|
||||
export function I18nPlugin(vue: typeof _Vue, store: Store<IRootState>): void {
|
||||
const i18n = new I18nClass(store);
|
||||
|
||||
@@ -43,10 +46,6 @@ export class I18nClass {
|
||||
return this.i18n.te(key);
|
||||
}
|
||||
|
||||
number(value: number, options: VueI18n.FormattedNumberPartType) {
|
||||
return this.i18n.n(value, options);
|
||||
}
|
||||
|
||||
shortNodeType(longNodeType: string) {
|
||||
return longNodeType.replace('n8n-nodes-base.', '');
|
||||
}
|
||||
@@ -56,16 +55,17 @@ export class I18nClass {
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* Render a string of base text, i.e. a string with a fixed path to the localized value in the base text object. Optionally allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces.
|
||||
* Render a string of base text, i.e. a string with a fixed path to the localized value. Optionally allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces.
|
||||
*/
|
||||
baseText(
|
||||
key: string, options?: { interpolate: { [key: string]: string } },
|
||||
key: string,
|
||||
options?: { interpolate: { [key: string]: string } },
|
||||
): string {
|
||||
return this.i18n.t(key, options && options.interpolate).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a string of dynamic text, i.e. a string with a constructed path to the localized value in the node text object, in the credentials modal, in the node view, or in the headers. Unlike in `baseText`, the fallback has to be set manually for dynamic text.
|
||||
* Render a string of dynamic text, i.e. a string with a constructed path to the localized value.
|
||||
*/
|
||||
private dynamicRender(
|
||||
{ key, fallback }: { key: string; fallback: string; },
|
||||
@@ -74,30 +74,32 @@ export class I18nClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a string of dynamic header text, used in the nodes panel and in the node view.
|
||||
* Render a string of header text (a node's name and description),
|
||||
* used variously in the nodes panel, under the node icon, etc.
|
||||
*/
|
||||
headerText(arg: { key: string; fallback: string; }) {
|
||||
return this.dynamicRender(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Namespace for methods to render text in the credentials details modal.
|
||||
*/
|
||||
credText () {
|
||||
const { credentialTextRenderKeys: keys } = this.$store.getters;
|
||||
const nodeType = keys ? keys.nodeType : '';
|
||||
const credentialType = keys ? keys.credentialType : '';
|
||||
const credentialPrefix = `${nodeType}.${CREDENTIALS_MODAL_KEY}.${credentialType}`;
|
||||
const credentialType = this.$store.getters.activeCredentialType;
|
||||
const credentialPrefix = `n8n-nodes-base.credentials.${credentialType}`;
|
||||
const context = this;
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Display name for a top-level parameter in the credentials modal.
|
||||
* Display name for a top-level param.
|
||||
*/
|
||||
topParameterDisplayName(
|
||||
inputLabelDisplayName(
|
||||
{ name: parameterName, displayName }: { name: string; displayName: string; },
|
||||
) {
|
||||
if (['clientId', 'clientSecret'].includes(parameterName)) {
|
||||
return context.dynamicRender({
|
||||
key: `${REUSABLE_DYNAMIC_TEXT_KEY}.oauth2.${parameterName}`,
|
||||
key: `reusableDynamicText.oauth2.${parameterName}`,
|
||||
fallback: displayName,
|
||||
});
|
||||
}
|
||||
@@ -109,9 +111,9 @@ export class I18nClass {
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for a top-level parameter in the credentials modal.
|
||||
* Description (tooltip text) for an input label param.
|
||||
*/
|
||||
topParameterDescription(
|
||||
inputLabelDescription(
|
||||
{ name: parameterName, description }: { name: string; description: string; },
|
||||
) {
|
||||
return context.dynamicRender({
|
||||
@@ -121,7 +123,7 @@ export class I18nClass {
|
||||
},
|
||||
|
||||
/**
|
||||
* Display name for an option inside an `options` or `multiOptions` parameter in the credentials modal.
|
||||
* Display name for an option inside an `options` or `multiOptions` param.
|
||||
*/
|
||||
optionsOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
@@ -134,7 +136,7 @@ export class I18nClass {
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for an option inside an `options` or `multiOptions` parameter in the credentials modal.
|
||||
* Description for an option inside an `options` or `multiOptions` param.
|
||||
*/
|
||||
optionsOptionDescription(
|
||||
{ name: parameterName }: { name: string; },
|
||||
@@ -147,7 +149,7 @@ export class I18nClass {
|
||||
},
|
||||
|
||||
/**
|
||||
* Placeholder for a `string` or `collection` or `fixedCollection` parameter in the credentials modal.
|
||||
* Placeholder for a `string` or `collection` or `fixedCollection` param.
|
||||
* - For a `string` parameter, the placeholder is unselectable greyed-out sample text.
|
||||
* - For a `collection` or `fixedCollection` parameter, the placeholder is the button text.
|
||||
*/
|
||||
@@ -162,99 +164,158 @@ export class I18nClass {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Namespace for methods to render text in the node details view,
|
||||
* except for `eventTriggerDescription`.
|
||||
*/
|
||||
nodeText () {
|
||||
const type = this.$store.getters.activeNode.type;
|
||||
const nodePrefix = `${type}.${NODE_VIEW_KEY}`;
|
||||
const activeNode = this.$store.getters.activeNode;
|
||||
const nodeType = activeNode ? this.shortNodeType(activeNode.type) : ''; // unused in eventTriggerDescription
|
||||
const initialKey = `n8n-nodes-base.nodes.${nodeType}.nodeView`;
|
||||
const context = this;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Display name for a top-level parameter in the node view.
|
||||
* Display name for an input label, whether top-level or nested.
|
||||
*/
|
||||
topParameterDisplayName(
|
||||
{ name: parameterName, displayName }: { name: string; displayName: string; },
|
||||
inputLabelDisplayName(
|
||||
parameter: { name: string; displayName: string; type: string },
|
||||
path: string,
|
||||
) {
|
||||
const middleKey = deriveMiddleKey(path, parameter);
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.displayName`,
|
||||
fallback: displayName,
|
||||
key: `${initialKey}.${middleKey}.displayName`,
|
||||
fallback: parameter.displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for a top-level parameter in the node view in the node view.
|
||||
* Description (tooltip text) for an input label, whether top-level or nested.
|
||||
*/
|
||||
topParameterDescription(
|
||||
{ name: parameterName, description }: { name: string; description: string; },
|
||||
inputLabelDescription(
|
||||
parameter: { name: string; description: string; type: string },
|
||||
path: string,
|
||||
) {
|
||||
const middleKey = deriveMiddleKey(path, parameter);
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.description`,
|
||||
fallback: description,
|
||||
key: `${initialKey}.${middleKey}.description`,
|
||||
fallback: parameter.description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display name for an option inside a `collection` or `fixedCollection` parameter in the node view.
|
||||
* Placeholder for an input label or `collection` or `fixedCollection` param,
|
||||
* whether top-level or nested.
|
||||
* - For an input label, the placeholder is unselectable greyed-out sample text.
|
||||
* - For a `collection` or `fixedCollection`, the placeholder is the button text.
|
||||
*/
|
||||
collectionOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ name: optionName, displayName }: { name: string; displayName: string; },
|
||||
placeholder(
|
||||
parameter: { name: string; placeholder: string; type: string },
|
||||
path: string,
|
||||
) {
|
||||
let middleKey = parameter.name;
|
||||
|
||||
if (isNestedInCollectionLike(path)) {
|
||||
const pathSegments = normalize(path).split('.');
|
||||
middleKey = insertOptionsAndValues(pathSegments).join('.');
|
||||
}
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
key: `${initialKey}.${middleKey}.placeholder`,
|
||||
fallback: parameter.placeholder,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display name for an option inside an `options` or `multiOptions` parameter in the node view.
|
||||
* Display name for an option inside an `options` or `multiOptions` param,
|
||||
* whether top-level or nested.
|
||||
*/
|
||||
optionsOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
parameter: { name: string; },
|
||||
{ value: optionName, name: displayName }: { value: string; name: string; },
|
||||
path: string,
|
||||
) {
|
||||
let middleKey = parameter.name;
|
||||
|
||||
if (isNestedInCollectionLike(path)) {
|
||||
const pathSegments = normalize(path).split('.');
|
||||
middleKey = insertOptionsAndValues(pathSegments).join('.');
|
||||
}
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.displayName`,
|
||||
key: `${initialKey}.${middleKey}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for an option inside an `options` or `multiOptions` parameter in the node view.
|
||||
* Description for an option inside an `options` or `multiOptions` param,
|
||||
* whether top-level or nested.
|
||||
*/
|
||||
optionsOptionDescription(
|
||||
{ name: parameterName }: { name: string; },
|
||||
parameter: { name: string; },
|
||||
{ value: optionName, description }: { value: string; description: string; },
|
||||
path: string,
|
||||
) {
|
||||
let middleKey = parameter.name;
|
||||
|
||||
if (isNestedInCollectionLike(path)) {
|
||||
const pathSegments = normalize(path).split('.');
|
||||
middleKey = insertOptionsAndValues(pathSegments).join('.');
|
||||
}
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.description`,
|
||||
key: `${initialKey}.${middleKey}.options.${optionName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Text for a button to add another option inside a `collection` or `fixedCollection` parameter having`multipleValues: true` in the node view.
|
||||
* Display name for an option in the dropdown menu of a `collection` or
|
||||
* fixedCollection` param. No nesting support since `collection` cannot
|
||||
* be nested in a `collection` or in a `fixedCollection`.
|
||||
*/
|
||||
collectionOptionDisplayName(
|
||||
parameter: { name: string; },
|
||||
{ name: optionName, displayName }: { name: string; displayName: string; },
|
||||
path: string,
|
||||
) {
|
||||
let middleKey = parameter.name;
|
||||
|
||||
if (isNestedInCollectionLike(path)) {
|
||||
const pathSegments = normalize(path).split('.');
|
||||
middleKey = insertOptionsAndValues(pathSegments).join('.');
|
||||
}
|
||||
|
||||
return context.dynamicRender({
|
||||
key: `${initialKey}.${middleKey}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Text for a button to add another option inside a `collection` or
|
||||
* `fixedCollection` param having `multipleValues: true`.
|
||||
*/
|
||||
multipleValueButtonText(
|
||||
{ name: parameterName, typeOptions: { multipleValueButtonText } }:
|
||||
{ name: string; typeOptions: { multipleValueButtonText: string; } },
|
||||
) {
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.multipleValueButtonText`,
|
||||
key: `${initialKey}.${parameterName}.multipleValueButtonText`,
|
||||
fallback: multipleValueButtonText,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Placeholder for a `string` or `collection` or `fixedCollection` parameter in the node view.
|
||||
* - For a `string` parameter, the placeholder is unselectable greyed-out sample text.
|
||||
* - For a `collection` or `fixedCollection` parameter, the placeholder is the button text.
|
||||
*/
|
||||
placeholder(
|
||||
{ name: parameterName, placeholder }: { name: string; placeholder: string; },
|
||||
eventTriggerDescription(
|
||||
nodeType: string,
|
||||
eventTriggerDescription: string,
|
||||
) {
|
||||
return context.dynamicRender({
|
||||
key: `${nodePrefix}.${parameterName}.placeholder`,
|
||||
fallback: placeholder,
|
||||
key: `n8n-nodes-base.nodes.${nodeType}.nodeView.eventTriggerDescription`,
|
||||
fallback: eventTriggerDescription,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -302,14 +363,25 @@ export async function loadLanguage(language?: string) {
|
||||
setLanguage(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node translation to the i18n instance's `messages` object.
|
||||
*/
|
||||
export function addNodeTranslation(
|
||||
nodeTranslation: { [key: string]: object },
|
||||
nodeTranslation: { [nodeType: string]: object },
|
||||
language: string,
|
||||
) {
|
||||
const oldNodesBase = i18nInstance.messages[language]['n8n-nodes-base'] || {};
|
||||
|
||||
const updatedNodes = {
|
||||
// @ts-ignore
|
||||
...oldNodesBase.nodes,
|
||||
...nodeTranslation,
|
||||
};
|
||||
|
||||
const newNodesBase = {
|
||||
'n8n-nodes-base': Object.assign(
|
||||
i18nInstance.messages[language]['n8n-nodes-base'] || {},
|
||||
nodeTranslation,
|
||||
oldNodesBase,
|
||||
{ nodes: updatedNodes },
|
||||
),
|
||||
};
|
||||
|
||||
@@ -319,6 +391,37 @@ export function addNodeTranslation(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a credential translation to the i18n instance's `messages` object.
|
||||
*/
|
||||
export function addCredentialTranslation(
|
||||
nodeCredentialTranslation: { [credentialType: string]: object },
|
||||
language: string,
|
||||
) {
|
||||
const oldNodesBase = i18nInstance.messages[language]['n8n-nodes-base'] || {};
|
||||
|
||||
const updatedCredentials = {
|
||||
// @ts-ignore
|
||||
...oldNodesBase.credentials,
|
||||
...nodeCredentialTranslation,
|
||||
};
|
||||
|
||||
const newNodesBase = {
|
||||
'n8n-nodes-base': Object.assign(
|
||||
oldNodesBase,
|
||||
{ credentials: updatedCredentials },
|
||||
),
|
||||
};
|
||||
|
||||
i18nInstance.setLocaleMessage(
|
||||
language,
|
||||
Object.assign(i18nInstance.messages[language], newNodesBase),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node's header strings to the i18n instance's `messages` object.
|
||||
*/
|
||||
export function addHeaders(
|
||||
headers: INodeTranslationHeaders,
|
||||
language: string,
|
||||
@@ -327,4 +430,4 @@ export function addHeaders(
|
||||
language,
|
||||
Object.assign(i18nInstance.messages[language], { headers }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user