Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore i18n post-UM #3072

Merged
merged 15 commits into from
Apr 11, 2022
7 changes: 5 additions & 2 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
/* eslint-disable no-await-in-loop */

import * as express from 'express';
import { readFileSync } from 'fs';
import { existsSync, readFileSync } from 'fs';
import { readFile } from 'fs/promises';
import { cloneDeep } from 'lodash';
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
Expand Down Expand Up @@ -1503,10 +1503,13 @@ class App {
async (req: express.Request, res: express.Response): Promise<object | void> => {
const packagesPath = pathJoin(__dirname, '..', '..', '..');
const headersPath = pathJoin(packagesPath, 'nodes-base', 'dist', 'nodes', 'headers');

if (!existsSync(`${headersPath}.js`)) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivov Why are we using a sync method here? Could we not simply use an async one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, switched to promisified access, still working:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks for the fast fix!


try {
return require(headersPath);
} catch (error) {
res.status(500).send('Failed to find headers file');
res.status(500).send('Failed to load headers file');
}
},
),
Expand Down
8 changes: 6 additions & 2 deletions packages/editor-ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { showMessage } from './components/mixins/showMessage';
import { IUser } from './Interface';
import { mapGetters } from 'vuex';
import { userHelpers } from './components/mixins/userHelpers';
import { loadLanguage } from './plugins/i18n';

export default mixins(
showMessage,
Expand All @@ -40,7 +41,7 @@ export default mixins(
Modals,
},
computed: {
...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']),
...mapGetters('settings', ['defaultLocale', 'isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']),
...mapGetters('users', ['currentUser']),
},
data() {
Expand Down Expand Up @@ -79,7 +80,7 @@ export default mixins(
}
},
logHiringBanner() {
if (!this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
if (this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change logic here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the original implementation, export N8N_HIRING_BANNER_ENABLED=false does not disable the banner.

console.log(HIRING_BANNER); // eslint-disable-line no-console
}
},
Expand Down Expand Up @@ -160,6 +161,9 @@ export default mixins(

this.trackPage();
},
'$store.getters.defaultLocale'(newLocale) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did this not work?

Suggested change
'$store.getters.defaultLocale'(newLocale) {
defaultLocale(newLocale) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The watcher cannot read from the mapped getter directly:

image

Either using the full store path as I did, or adding an extra getter just for the watcher.

defaultLocale(): string {
	return this.$store.getters.defaultLocale;
},

loadLanguage(newLocale);
},
},
});
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,7 @@ export default Vue.extend({
},
rows(): ITagRow[] {
const getUsage = (count: number | undefined) => count && count > 0
? this.$locale.baseText(
count > 1 ?
'tagsView.inUse.plural' : 'tagsView.inUse.singular',
{
interpolate: {
count: count.toString(),
},
},
)
? this.$locale.baseText('tagsView.inUse', { adjustToNumber: count })
: this.$locale.baseText('tagsView.notBeingUsed');

const disabled = this.isCreateEnabled || this.$data.updateId || this.$data.deleteId;
Expand Down
66 changes: 35 additions & 31 deletions packages/editor-ui/src/plugins/i18n/docs/ADDENDUM.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

## Base text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for updating the guide. can you also update section that says we should use first words of a message? I think I saw this in a previous PR. Instead names should be more descriptive.

Copy link
Contributor Author

@ivov ivov Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That section was in the other PR that was not merged. I intentionally left that part out.

Edit: To expand, the current keys still reference file structure. Not sure if we should add any recommendations that are not being followed yet.


### Pluralization

Certain base text strings accept [singular and plural versions](https://kazupon.github.io/vue-i18n/guide/pluralization.html) separated by a `|` character:

```json
{
"tagsView.inUse": "{count} workflow | {count} workflows",
}
```

### Interpolation

Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable to be passed in, signalled by curly braces:
Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable between curly braces:

```json
{
"stopExecution": {
"message": "The execution with the ID {activeExecutionId} got stopped!",
"title": "Execution stopped"
}
"stopExecution.message": "The execution with the ID {activeExecutionId} got stopped!",
"stopExecution.title": "Execution stopped"
}
```

When translating a string containing an interpolated variable, leave the variable untranslated:

```json
{
"stopExecution": {
"message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt",
"title": "Execution stopped"
}
"stopExecution.message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt",
"stopExecution.title": "Execution stopped"
}
```

Expand All @@ -32,18 +38,12 @@ As a convenience, the base text file may contain the special key `reusableBaseTe

```json
{
"reusableBaseText": {
"save": "🇩🇪 Save",
},
"duplicateWorkflowDialog": {
"enterWorkflowName": "🇩🇪 Enter workflow name",
"save": "@:reusableBaseText.save",
},
"saveButton": {
"save": "@:reusableBaseText.save",
"saving": "🇩🇪 Saving",
"saved": "🇩🇪 Saved",
},
"reusableBaseText.save": "🇩🇪 Save",
"duplicateWorkflowDialog.enterWorkflowName": "🇩🇪 Enter workflow name",
"duplicateWorkflowDialog.save": "@:reusableBaseText.save",
"saveButton.save": "@:reusableBaseText.save",
"saveButton.saving": "🇩🇪 Saving",
"saveButton.saved": "🇩🇪 Saved",
}
```

Expand Down Expand Up @@ -92,23 +92,27 @@ Currently only the keys `oauth.clientId` and `oauth.clientSecret` are supported

```json
{
"reusableDynamicText": {
"oauth2": {
"clientId": "🇩🇪 Client ID",
"clientSecret": "🇩🇪 Client Secret",
}
}
"reusableDynamicText.oauth2.clientId": "🇩🇪 Client ID",
"reusableDynamicText.oauth2.clientSecret": "🇩🇪 Client Secret",
}
```

### Special cases

`eventTriggerDescription` is a dynamic node property that is not part of node parameters. To translate it, set the `eventTriggerDescription` key at the root level of the `nodeView` property in the node translation file.
`eventTriggerDescription` and `activationMessage` are dynamic node properties that are not part of node parameters. To translate them, set the key at the root level of the `nodeView` property in the node translation file.

Webhook node:

```json
{
"nodeView.eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL",
}
```

Cron node:

```json
{
"nodeView": {
"eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL"
}
"nodeView.activationMessage": "🇩🇪 'Your cron trigger will now trigger executions on the schedule you have defined."
}
```
127 changes: 39 additions & 88 deletions packages/editor-ui/src/plugins/i18n/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ n8n allows for internalization of the majority of UI text:

- base text, e.g. menu display items in the left-hand sidebar menu,
- node text, e.g. parameter display names and placeholders in the node view,
- credential text, e.g. parameter display names and placeholders in the credential modal,
- header text, e.g. node display names and descriptions at various spots.

Currently, n8n does _not_ allow for internalization of:
Expand Down Expand Up @@ -55,12 +56,10 @@ Base text is rendered with no dependencies, i.e. base text is fixed and does not
The base text file for each locale is located at `/packages/editor-ui/src/plugins/i18n/locales/` and is named `{localeIdentifier}.json`. Keys in the base text file can be Vue component dirs, Vue component names, and references to symbols in those Vue components. These keys are added by the team as the UI is modified or expanded.

```json
"nodeCreator": {
"categoryNames": {
"analytics": "🇩🇪 Analytics",
"communication": "🇩🇪 Communication",
"coreNodes": "🇩🇪 Core Nodes"
}
{
"nodeCreator.categoryNames.analytics": "🇩🇪 Analytics",
"nodeCreator.categoryNames.communication": "🇩🇪 Communication",
"nodeCreator.categoryNames.coreNodes": "🇩🇪 Core Nodes"
}
```

Expand Down Expand Up @@ -98,9 +97,9 @@ A credential translation file is placed at `/nodes-base/credentials/translations
```
credentials
└── translations
└── de
├── githubApi.json
└── githubOAuth2Api.json
└── de
├── githubApi.json
└── githubOAuth2Api.json
```
Every credential must have its own credential translation file.

Expand All @@ -123,9 +122,9 @@ GitHub
├── GitHub.node.ts
├── GitHubTrigger.node.ts
└── translations
└── de
├── github.json
└── githubTrigger.json
└── de
├── github.json
└── githubTrigger.json
```

Every node must have its own node translation file.
Expand Down Expand Up @@ -184,16 +183,10 @@ The object for each node credential parameter allows for the keys `displayName`,

```json
{
"server": {
"displayName": "🇩🇪 Github Server",
"description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.",
},
"user": {
"placeholder": "🇩🇪 Hans",
},
"accessToken": {
"placeholder": "🇩🇪 123",
},
"server.displayName": "🇩🇪 Github Server",
"server.description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.",
"user.placeholder": "🇩🇪 Hans",
"accessToken.placeholder": "🇩🇪 123",
}
```

Expand Down Expand Up @@ -224,10 +217,8 @@ export class Github implements INodeType {

```json
{
"header": {
"displayName": "🇩🇪 GitHub",
"description": "🇩🇪 Consume GitHub API",
},
"header.displayName": "🇩🇪 GitHub",
"header.description": "🇩🇪 Consume GitHub API",
}
```

Expand Down Expand Up @@ -264,11 +255,7 @@ export class Github implements INodeType {

```json
{
"nodeView": {
"resource": {
"displayName": "🇩🇪 Resource",
},
},
"nodeView.resource.displayName": "🇩🇪 Resource",
}
```

Expand All @@ -291,13 +278,9 @@ Allowed keys: `displayName`, `description`, `placeholder`

```json
{
"nodeView": {
"owner": {
"displayName": "🇩🇪 Repository Owner",
"placeholder": "🇩🇪 n8n-io",
"description": "🇩🇪 Owner of the repository",
},
},
"nodeView.owner.displayName": "🇩🇪 Repository Owner",
"nodeView.owner.placeholder": "🇩🇪 n8n-io",
"nodeView.owner.description": "🇩🇪 Owner of the repository",
}
```

Expand Down Expand Up @@ -333,20 +316,10 @@ Allowed subkeys: `options.{optionName}.displayName` and `options.{optionName}.de

```json
{
"nodeView": {
"resource": {
"displayName": "🇩🇪 Resource",
"description": "🇩🇪 Resource to operate on",
"options": {
"file": {
"displayName": "🇩🇪 File",
},
"issue": {
"displayName": "🇩🇪 Issue",
},
},
},
},
"nodeView.resource.displayName": "🇩🇪 Resource",
"nodeView.resource.description": "🇩🇪 Resource to operate on",
"nodeView.resource.options.file.displayName": "🇩🇪 File",
"nodeView.resource.options.issue.displayName": "🇩🇪 Issue",
}
```

Expand Down Expand Up @@ -394,19 +367,11 @@ Example of `collection` parameter:

```json
{
"nodeView": {
"labels": {
"displayName": "🇩🇪 Labels",
"multipleValueButtonText": "🇩🇪 Add Label",
"options": {
"label": {
"displayName": "🇩🇪 Label",
"description": "🇩🇪 Label to add to issue",
"placeholder": "🇩🇪 Some placeholder"
}
}
}
}
"nodeView.labels.displayName": "🇩🇪 Labels",
"nodeView.labels.multipleValueButtonText": "🇩🇪 Add Label",
"nodeView.labels.options.label.displayName": "🇩🇪 Label",
"nodeView.labels.options.label.description": "🇩🇪 Label to add to issue",
"nodeView.labels.options.label.placeholder": "🇩🇪 Some placeholder"
}
```

Expand Down Expand Up @@ -461,29 +426,15 @@ Example of `fixedCollection` parameter:

```json
{
"nodeView": {
"additionalParameters": {
"displayName": "🇩🇪 Additional Parameters",
"placeholder": "🇩🇪 Add Field",
"options": {
"author": {
"displayName": "🇩🇪 Author",
"values": {
"name": {
"displayName": "🇩🇪 Name",
"description": "🇩🇪 Name of the author of the commit",
"placeholder": "🇩🇪 Jan"
},
"email": {
"displayName": "🇩🇪 Email",
"description": "🇩🇪 Email of the author of the commit",
"placeholder": "🇩🇪 jan@n8n.io"
}
}
},
}
}
}
"nodeView.additionalParameters.displayName": "🇩🇪 Additional Parameters",
"nodeView.additionalParameters.placeholder": "🇩🇪 Add Field",
"nodeView.additionalParameters.options.author.displayName": "🇩🇪 Author",
"nodeView.additionalParameters.options.author.values.name.displayName": "🇩🇪 Name",
"nodeView.additionalParameters.options.author.values.name.description": "🇩🇪 Name of the author of the commit",
"nodeView.additionalParameters.options.author.values.name.placeholder": "🇩🇪 Jan",
"nodeView.additionalParameters.options.author.values.email.displayName": "🇩🇪 Email",
"nodeView.additionalParameters.options.author.values.email.description": "🇩🇪 Email of the author of the commit",
"nodeView.additionalParameters.options.author.values.email.placeholder": "🇩🇪 jan@n8n.io",
}
```

Expand Down
Loading