mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
⚡ Add autocompletion for i18n keys in script sections of Vue files (#3133)
* 📘 Type `baseText()` to i18n keys * 📘 Adjust `baseText()` signature * 👕 Except JSON files from Vue ESLint * 🐛 Fix errors surfaced by `baseText()` typing * ⚡ Pluralize keys * 📘 Add typing for category names * ⚡ Mark internal keys * ✏️ Update docs references * 🎨 Prettify syntax * 🐛 Fix leftover internal key references
This commit is contained in:
parent
8532b0030d
commit
18dee373d5
|
@ -90,8 +90,11 @@ export default mixins(showMessage).extend({
|
|||
return this.userToDelete && !this.userToDelete.firstName;
|
||||
},
|
||||
title(): string {
|
||||
const user = this.userToDelete && (this.userToDelete.fullName || this.userToDelete.email);
|
||||
return this.$locale.baseText('settings.users.deleteUser', { interpolate: { user }});
|
||||
const user = this.userToDelete && (this.userToDelete.fullName || this.userToDelete.email) || '';
|
||||
return this.$locale.baseText(
|
||||
'settings.users.deleteUser',
|
||||
{ interpolate: { user }},
|
||||
);
|
||||
},
|
||||
enabled(): boolean {
|
||||
if (this.isPending) {
|
||||
|
@ -138,7 +141,10 @@ export default mixins(showMessage).extend({
|
|||
if (this.transferId) {
|
||||
const getUserById = this.$store.getters['users/getUserById'];
|
||||
const transferUser: IUser = getUserById(this.transferId);
|
||||
message = this.$locale.baseText('settings.users.transferredToUser', { interpolate: { user: transferUser.fullName }});
|
||||
message = this.$locale.baseText(
|
||||
'settings.users.transferredToUser',
|
||||
{ interpolate: { user: transferUser.fullName || '' }},
|
||||
);
|
||||
}
|
||||
|
||||
this.$showMessage({
|
||||
|
|
|
@ -106,7 +106,10 @@ export default mixins(showMessage).extend({
|
|||
},
|
||||
buttonLabel(): string {
|
||||
if (this.emailsCount > 1) {
|
||||
return this.$locale.baseText('settings.users.inviteXUser', { interpolate: { count: this.emailsCount }});
|
||||
return this.$locale.baseText(
|
||||
'settings.users.inviteXUser',
|
||||
{ interpolate: { count: this.emailsCount.toString() }},
|
||||
);
|
||||
}
|
||||
|
||||
return this.$locale.baseText('settings.users.inviteUser');
|
||||
|
|
|
@ -125,13 +125,16 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
|||
if (this.nodeType !== null && this.nodeType.hasOwnProperty('eventTriggerDescription')) {
|
||||
const nodeName = this.$locale.shortNodeType(this.nodeType.name);
|
||||
const { eventTriggerDescription } = this.nodeType;
|
||||
return this.$locale.nodeText().eventTriggerDescription(nodeName, eventTriggerDescription);
|
||||
return this.$locale.nodeText().eventTriggerDescription(
|
||||
nodeName,
|
||||
eventTriggerDescription || '',
|
||||
);
|
||||
} else {
|
||||
return this.$locale.baseText(
|
||||
'node.waitingForYouToCreateAnEventIn',
|
||||
{
|
||||
interpolate: {
|
||||
nodeType: this.nodeType && getTriggerNodeServiceName(this.nodeType.displayName),
|
||||
nodeType: this.nodeType ? getTriggerNodeServiceName(this.nodeType.displayName) : '',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import camelcase from 'lodash.camelcase';
|
||||
import { CategoryName } from '@/plugins/i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['item'],
|
||||
|
@ -24,8 +25,8 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
renderCategoryName(categoryName: string) {
|
||||
const key = `nodeCreator.categoryNames.${categoryName}`;
|
||||
renderCategoryName(categoryName: CategoryName) {
|
||||
const key = `nodeCreator.categoryNames.${categoryName}` as const;
|
||||
|
||||
return this.$locale.exists(key) ? this.$locale.baseText(key) : categoryName;
|
||||
},
|
||||
|
|
|
@ -330,7 +330,7 @@ export default mixins(
|
|||
interpolate: {
|
||||
defaultValue: this.defaultValues.saveDataErrorExecution === 'all'
|
||||
? this.$locale.baseText('workflowSettings.saveDataErrorExecutionOptions.save')
|
||||
: this.$locale.baseText('workflowSettings.saveDataErrorExecutionOptions.doNotsave'),
|
||||
: this.$locale.baseText('workflowSettings.saveDataErrorExecutionOptions.doNotSave'),
|
||||
},
|
||||
},
|
||||
),
|
||||
|
|
|
@ -34,14 +34,14 @@ When translating a string containing an interpolated variable, leave the variabl
|
|||
|
||||
### Reusable base text
|
||||
|
||||
As a convenience, the base text file may contain the special key `reusableBaseText`, which defines strings that can be shared among other strings with the syntax `@:reusableBaseText.key`, as follows:
|
||||
As a convenience, the base text file may contain the special key `_reusableBaseText`, which defines strings that can be shared among other strings with the syntax `@:_reusableBaseText.key`, as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"reusableBaseText.save": "🇩🇪 Save",
|
||||
"_reusableBaseText.save": "🇩🇪 Save",
|
||||
"duplicateWorkflowDialog.enterWorkflowName": "🇩🇪 Enter workflow name",
|
||||
"duplicateWorkflowDialog.save": "@:reusableBaseText.save",
|
||||
"saveButton.save": "@:reusableBaseText.save",
|
||||
"duplicateWorkflowDialog.save": "@:_reusableBaseText.save",
|
||||
"saveButton.save": "@:_reusableBaseText.save",
|
||||
"saveButton.saving": "🇩🇪 Saving",
|
||||
"saveButton.saved": "🇩🇪 Saved",
|
||||
}
|
||||
|
@ -92,8 +92,8 @@ Currently only the keys `oauth.clientId` and `oauth.clientSecret` are supported
|
|||
|
||||
```json
|
||||
{
|
||||
"reusableDynamicText.oauth2.clientId": "🇩🇪 Client ID",
|
||||
"reusableDynamicText.oauth2.clientSecret": "🇩🇪 Client Secret",
|
||||
"_reusableDynamicText.oauth2.clientId": "🇩🇪 Client ID",
|
||||
"_reusableDynamicText.oauth2.clientSecret": "🇩🇪 Client Secret",
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
locale,
|
||||
} from 'n8n-design-system';
|
||||
|
||||
const englishBaseText = require('./locales/en');
|
||||
import englishBaseText from './locales/en.json';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
locale.use('en');
|
||||
|
@ -62,8 +62,8 @@ export class I18nClass {
|
|||
* 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?: { adjustToNumber: number; interpolate: { [key: string]: string } },
|
||||
key: BaseTextKey,
|
||||
options?: { adjustToNumber?: number; interpolate?: { [key: string]: string } },
|
||||
): string {
|
||||
if (options && options.adjustToNumber) {
|
||||
return this.i18n.tc(key, options.adjustToNumber, options && options.interpolate).toString();
|
||||
|
@ -107,7 +107,7 @@ export class I18nClass {
|
|||
) {
|
||||
if (['clientId', 'clientSecret'].includes(parameterName)) {
|
||||
return context.dynamicRender({
|
||||
key: `reusableDynamicText.oauth2.${parameterName}`,
|
||||
key: `_reusableDynamicText.oauth2.${parameterName}`,
|
||||
fallback: displayName,
|
||||
});
|
||||
}
|
||||
|
@ -469,3 +469,23 @@ export function addHeaders(
|
|||
Object.assign(i18nInstance.messages[language], { headers }),
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// typings
|
||||
// ----------------------------------
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$locale: I18nClass;
|
||||
}
|
||||
}
|
||||
|
||||
type GetBaseTextKey<T> = T extends `_${string}` ? never : T;
|
||||
|
||||
export type BaseTextKey = GetBaseTextKey<keyof typeof englishBaseText>;
|
||||
|
||||
type GetCategoryName<T> = T extends `nodeCreator.categoryNames.${infer C}`
|
||||
? C
|
||||
: never;
|
||||
|
||||
export type CategoryName = GetCategoryName<keyof typeof englishBaseText>;
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"_reusableBaseText.cancel": "Cancel",
|
||||
"_reusableBaseText.name": "Name",
|
||||
"_reusableBaseText.save": "Save",
|
||||
"_reusableDynamicText.oauth2.clientId": "Client ID",
|
||||
"_reusableDynamicText.oauth2.clientSecret": "Client Secret",
|
||||
"about.aboutN8n": "About n8n",
|
||||
"about.close": "Close",
|
||||
"about.license": "License",
|
||||
|
@ -44,10 +49,8 @@
|
|||
"auth.setup.ownerAccountBenefits": "Setting up an owner account allows you to invite others, and prevents people using n8n without an account",
|
||||
"auth.setup.settingUpOwnerError": "Problem setting up owner",
|
||||
"auth.setup.setupConfirmation.concatEntities": "{workflows} and {credentials}",
|
||||
"auth.setup.setupConfirmation.credentialsCount": "{count} credentials",
|
||||
"auth.setup.setupConfirmation.oneCredentialCount": "{count} credential",
|
||||
"auth.setup.setupConfirmation.oneWorkflowCount": "{count} existing workflow",
|
||||
"auth.setup.setupConfirmation.workflowsCount": "{count} existing workflows",
|
||||
"auth.setup.setupConfirmation.credentials": "{count} credential | {count} credentials",
|
||||
"auth.setup.setupConfirmation.existingWorkflows": "{count} existing workflow | {count} existing workflows",
|
||||
"auth.setup.setupOwner": "Set up owner account",
|
||||
"auth.setup.skipOwnerSetupQuestion": "Skip owner account setup?",
|
||||
"auth.setup.skipSetup": "Skip setup",
|
||||
|
@ -131,7 +134,7 @@
|
|||
"credentialsList.deleteCredential": "Delete Credential",
|
||||
"credentialsList.editCredential": "Edit Credential",
|
||||
"credentialsList.errorLoadingCredentials": "Error loading credentials",
|
||||
"credentialsList.name": "@:reusableBaseText.name",
|
||||
"credentialsList.name": "@:_reusableBaseText.name",
|
||||
"credentialsList.operations": "Operations",
|
||||
"credentialsList.showError.deleteCredential.title": "Problem deleting credential",
|
||||
"credentialsList.showMessage.title": "Credential deleted",
|
||||
|
@ -144,11 +147,11 @@
|
|||
"displayWithChange.cancelEdit": "Cancel Edit",
|
||||
"displayWithChange.clickToChange": "Click to Change",
|
||||
"displayWithChange.setValue": "Set Value",
|
||||
"duplicateWorkflowDialog.cancel": "@:reusableBaseText.cancel",
|
||||
"duplicateWorkflowDialog.cancel": "@:_reusableBaseText.cancel",
|
||||
"duplicateWorkflowDialog.chooseOrCreateATag": "Choose or create a tag",
|
||||
"duplicateWorkflowDialog.duplicateWorkflow": "Duplicate Workflow",
|
||||
"duplicateWorkflowDialog.enterWorkflowName": "Enter workflow name",
|
||||
"duplicateWorkflowDialog.save": "@:reusableBaseText.save",
|
||||
"duplicateWorkflowDialog.save": "@:_reusableBaseText.save",
|
||||
"duplicateWorkflowDialog.showMessage.message": "Please enter a name.",
|
||||
"duplicateWorkflowDialog.showMessage.title": "Name missing",
|
||||
"error": "Error",
|
||||
|
@ -181,7 +184,7 @@
|
|||
"executionsList.modes.retry": "retry",
|
||||
"executionsList.modes.trigger": "trigger",
|
||||
"executionsList.modes.webhook": "webhook",
|
||||
"executionsList.name": "@:reusableBaseText.name",
|
||||
"executionsList.name": "@:_reusableBaseText.name",
|
||||
"executionsList.openPastExecution": "Open Past Execution",
|
||||
"executionsList.retryExecution": "Retry execution",
|
||||
"executionsList.retryOf": "Retry of",
|
||||
|
@ -267,12 +270,12 @@
|
|||
"mainSidebar.new": "New",
|
||||
"mainSidebar.newTemplate": "New from template",
|
||||
"mainSidebar.open": "Open",
|
||||
"mainSidebar.prompt.cancel": "@:reusableBaseText.cancel",
|
||||
"mainSidebar.prompt.cancel": "@:_reusableBaseText.cancel",
|
||||
"mainSidebar.prompt.import": "Import",
|
||||
"mainSidebar.prompt.importWorkflowFromUrl": "Import Workflow from URL",
|
||||
"mainSidebar.prompt.invalidUrl": "Invalid URL",
|
||||
"mainSidebar.prompt.workflowUrl": "Workflow URL",
|
||||
"mainSidebar.save": "@:reusableBaseText.save",
|
||||
"mainSidebar.save": "@:_reusableBaseText.save",
|
||||
"mainSidebar.settings": "Settings",
|
||||
"mainSidebar.showError.stopExecution.title": "Problem stopping execution",
|
||||
"mainSidebar.showMessage.handleFileImport.message": "The file does not contain valid JSON data",
|
||||
|
@ -427,7 +430,7 @@
|
|||
"nodeView.loadingTemplate": "Loading template",
|
||||
"nodeView.moreInfo": "More info",
|
||||
"nodeView.noNodesGivenToAdd": "No nodes to add specified",
|
||||
"nodeView.prompt.cancel": "@:reusableBaseText.cancel",
|
||||
"nodeView.prompt.cancel": "@:_reusableBaseText.cancel",
|
||||
"nodeView.prompt.invalidName": "Invalid Name",
|
||||
"nodeView.prompt.newName": "New Name",
|
||||
"nodeView.prompt.rename": "Rename",
|
||||
|
@ -573,11 +576,6 @@
|
|||
"pushConnection.showMessage.title": "Workflow executed successfully",
|
||||
"pushConnectionTracker.cannotConnectToServer": "You have a connection issue or the server is down. <br />n8n should reconnect automatically once the issue is resolved.",
|
||||
"pushConnectionTracker.connectionLost": "Connection lost",
|
||||
"reusableBaseText.cancel": "Cancel",
|
||||
"reusableBaseText.name": "Name",
|
||||
"reusableBaseText.save": "Save",
|
||||
"reusableDynamicText.oauth2.clientId": "Client ID",
|
||||
"reusableDynamicText.oauth2.clientSecret": "Client Secret",
|
||||
"runData.binary": "Binary",
|
||||
"runData.copyItemPath": "Copy Item Path",
|
||||
"runData.copyParameterPath": "Copy Parameter Path",
|
||||
|
@ -600,7 +598,7 @@
|
|||
"runData.showBinaryData": "View",
|
||||
"runData.startTime": "Start Time",
|
||||
"runData.table": "Table",
|
||||
"saveButton.save": "@:reusableBaseText.save",
|
||||
"saveButton.save": "@:_reusableBaseText.save",
|
||||
"saveButton.saved": "Saved",
|
||||
"saveButton.saving": "Saving",
|
||||
"settings": "Settings",
|
||||
|
@ -649,7 +647,7 @@
|
|||
"settings.users.usersInvited": "Users invited",
|
||||
"settings.users.usersInvitedError": "Could not invite users",
|
||||
"settings.version": "Version",
|
||||
"showMessage.cancel": "@:reusableBaseText.cancel",
|
||||
"showMessage.cancel": "@:_reusableBaseText.cancel",
|
||||
"showMessage.ok": "OK",
|
||||
"showMessage.showDetails": "Show Details",
|
||||
"startupError": "Error connecting to n8n",
|
||||
|
@ -674,11 +672,11 @@
|
|||
"tagsManager.showMessage.onUpdate.title": "Tag updated",
|
||||
"tagsManager.tagNameCannotBeEmpty": "Tag name cannot be empty",
|
||||
"tagsTable.areYouSureYouWantToDeleteThisTag": "Are you sure you want to delete this tag?",
|
||||
"tagsTable.cancel": "@:reusableBaseText.cancel",
|
||||
"tagsTable.cancel": "@:_reusableBaseText.cancel",
|
||||
"tagsTable.createTag": "Create tag",
|
||||
"tagsTable.deleteTag": "Delete tag",
|
||||
"tagsTable.editTag": "Edit Tag",
|
||||
"tagsTable.name": "@:reusableBaseText.name",
|
||||
"tagsTable.name": "@:_reusableBaseText.name",
|
||||
"tagsTable.noMatchingTagsExist": "No matching tags exist",
|
||||
"tagsTable.saveChanges": "Save changes?",
|
||||
"tagsTable.usage": "Usage",
|
||||
|
@ -784,7 +782,7 @@
|
|||
"workflowOpen.couldNotLoadActiveWorkflows": "Could not load active workflows",
|
||||
"workflowOpen.created": "Created",
|
||||
"workflowOpen.filterWorkflows": "Filter by tags",
|
||||
"workflowOpen.name": "@:reusableBaseText.name",
|
||||
"workflowOpen.name": "@:_reusableBaseText.name",
|
||||
"workflowOpen.openWorkflow": "Open Workflow",
|
||||
"workflowOpen.searchWorkflows": "Search workflows...",
|
||||
"workflowOpen.showError.title": "Problem loading workflows",
|
||||
|
@ -813,15 +811,15 @@
|
|||
"workflowSettings.hours": "hours",
|
||||
"workflowSettings.minutes": "minutes",
|
||||
"workflowSettings.noWorkflow": "- No Workflow -",
|
||||
"workflowSettings.save": "@:reusableBaseText.save",
|
||||
"workflowSettings.save": "@:_reusableBaseText.save",
|
||||
"workflowSettings.saveDataErrorExecution": "Save failed executions",
|
||||
"workflowSettings.saveDataErrorExecutionOptions.defaultSave": "Default - {defaultValue}",
|
||||
"workflowSettings.saveDataErrorExecutionOptions.doNotSave": "Do not save",
|
||||
"workflowSettings.saveDataErrorExecutionOptions.save": "@:reusableBaseText.save",
|
||||
"workflowSettings.saveDataErrorExecutionOptions.save": "@:_reusableBaseText.save",
|
||||
"workflowSettings.saveDataSuccessExecution": "Save successful executions",
|
||||
"workflowSettings.saveDataSuccessExecutionOptions.defaultSave": "Default - {defaultValue}",
|
||||
"workflowSettings.saveDataSuccessExecutionOptions.doNotSave": "Do not save",
|
||||
"workflowSettings.saveDataSuccessExecutionOptions.save": "@:reusableBaseText.save",
|
||||
"workflowSettings.saveDataSuccessExecutionOptions.save": "@:_reusableBaseText.save",
|
||||
"workflowSettings.saveExecutionProgress": "Save execution progress",
|
||||
"workflowSettings.saveExecutionProgressOptions.defaultSave": "Default - {defaultValue}",
|
||||
"workflowSettings.saveExecutionProgressOptions.no": "No",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import { i18nClass } from '.';
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$locale: I18nClass;
|
||||
}
|
||||
}
|
|
@ -83,7 +83,10 @@ export default mixins(showMessage).extend({
|
|||
this.$showToast({
|
||||
type: 'success',
|
||||
title: this.$locale.baseText('settings.users.inviteResent'),
|
||||
message: this.$locale.baseText('settings.users.emailSentTo', { interpolate: { email: user.email } }),
|
||||
message: this.$locale.baseText(
|
||||
'settings.users.emailSentTo',
|
||||
{ interpolate: { email: user.email || '' } },
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
this.$showError(e, this.$locale.baseText('settings.users.userReinviteError'));
|
||||
|
|
|
@ -103,8 +103,19 @@ export default mixins(
|
|||
return true;
|
||||
}
|
||||
|
||||
const workflows = this.workflowsCount > 0 ? this.$locale.baseText(this.workflowsCount === 1 ? 'auth.setup.setupConfirmation.oneWorkflowCount' : 'auth.setup.setupConfirmation.workflowsCount', { interpolate: { count: this.workflowsCount } }) : '';
|
||||
const credentials = this.credentialsCount > 0 ? this.$locale.baseText(this.credentialsCount === 1? 'auth.setup.setupConfirmation.oneCredentialCount' : 'auth.setup.setupConfirmation.credentialsCount', { interpolate: { count: this.credentialsCount } }) : '';
|
||||
const workflows = this.workflowsCount > 0
|
||||
? this.$locale.baseText(
|
||||
'auth.setup.setupConfirmation.existingWorkflows',
|
||||
{ adjustToNumber: this.workflowsCount },
|
||||
)
|
||||
: '';
|
||||
|
||||
const credentials = this.credentialsCount > 0
|
||||
? this.$locale.baseText(
|
||||
'auth.setup.setupConfirmation.credentials',
|
||||
{ adjustToNumber: this.credentialsCount },
|
||||
)
|
||||
: '';
|
||||
|
||||
const entities = workflows && credentials ? this.$locale.baseText('auth.setup.setupConfirmation.concatEntities', {interpolate: { workflows, credentials }}) : (workflows || credentials);
|
||||
return await this.confirmMessage(
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
"linterOptions": {
|
||||
"exclude": [
|
||||
"node_modules/**/*",
|
||||
"../../node_modules/**/*"
|
||||
"../../node_modules/**/*",
|
||||
"**/*.json"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "error",
|
||||
|
|
Loading…
Reference in a new issue