From 7d7d75d7e5c6fa3b968196377ecb18d25bcc3a26 Mon Sep 17 00:00:00 2001
From: qwerty287 <80460567+qwerty287@users.noreply.github.com>
Date: Mon, 16 May 2022 21:18:48 +0200
Subject: [PATCH] Support localized web UI (#912)
* Add support for localization
* Add docs & format code
* Add lib to docs
---
docs/docs/92-development/07-translations.md | 17 ++
web/.eslintignore | 1 +
web/package.json | 2 +
web/src/assets/locales/en.json | 208 ++++++++++++++++++
.../components/build-feed/BuildFeedItem.vue | 4 +-
.../build-feed/BuildFeedSidebar.vue | 2 +-
web/src/components/layout/header/Navbar.vue | 6 +-
web/src/components/repo/build/BuildList.vue | 2 +-
web/src/components/repo/build/BuildLog.vue | 6 +-
.../components/repo/settings/ActionsTab.vue | 18 +-
web/src/components/repo/settings/BadgeTab.vue | 8 +-
.../components/repo/settings/GeneralTab.vue | 117 +++++-----
.../repo/settings/RegistriesTab.vue | 45 ++--
.../components/repo/settings/SecretsTab.vue | 79 ++++---
web/src/compositions/useBuild.ts | 6 +-
web/src/compositions/useDate.ts | 6 +-
web/src/compositions/useI18n.ts | 12 +
web/src/main.ts | 2 +
web/src/utils/duration.ts | 28 +--
web/src/utils/timeAgo.ts | 2 +-
web/src/views/Login.vue | 18 +-
web/src/views/NotFound.vue | 8 +-
web/src/views/RepoAdd.vue | 16 +-
web/src/views/Repos.vue | 4 +-
web/src/views/ReposOwner.vue | 4 +-
web/src/views/User.vue | 19 +-
web/src/views/repo/RepoBranch.vue | 2 +-
web/src/views/repo/RepoSettings.vue | 12 +-
web/src/views/repo/RepoWrapper.vue | 8 +-
web/src/views/repo/build/Build.vue | 20 +-
.../views/repo/build/BuildChangedFiles.vue | 2 +-
web/src/views/repo/build/BuildWrapper.vue | 20 +-
web/vite.config.ts | 4 +
web/yarn.lock | 144 +++++++++++-
34 files changed, 660 insertions(+), 192 deletions(-)
create mode 100644 docs/docs/92-development/07-translations.md
create mode 100644 web/src/assets/locales/en.json
create mode 100644 web/src/compositions/useI18n.ts
diff --git a/docs/docs/92-development/07-translations.md b/docs/docs/92-development/07-translations.md
new file mode 100644
index 000000000..9b39c40a0
--- /dev/null
+++ b/docs/docs/92-development/07-translations.md
@@ -0,0 +1,17 @@
+# Translations
+
+Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library, thus you can easily translate the web UI into your language. Therefore, copy the file `web/src/assets/locales/en.json` to the same path with your language's code and `.json` as name.
+Then, translate content of this file, but only the values:
+
+```json
+{
+ "dont_translate": "Only translate this text"
+}
+```
+
+To add support for time formatting, import the language into two files:
+
+1. `web/src/compositions/useDate.ts`: Just add a line like `import 'dayjs/locale/en';` to the first block of `import` statements and replace `en` with your language's code.
+2. `web/src/utils/timeAgo.ts`: Add a line like `import en from 'javascript-time-ago/locale/en.json';` to the other `import`-statements and replace both `en`s with your language's code. Then, add the line `TimeAgo.addDefaultLocale(en);` to the other lines of them, and replace `en` with your language's code.
+
+Then, the web UI should be available in your language. You should open a pull request to our repository to get your changes into the next release.
diff --git a/web/.eslintignore b/web/.eslintignore
index 67bb21d0a..94e770c8b 100644
--- a/web/.eslintignore
+++ b/web/.eslintignore
@@ -4,3 +4,4 @@ coverage/
package.json
tsconfig.eslint.json
tsconfig.json
+src/assets/locales/
diff --git a/web/package.json b/web/package.json
index 69e8cc868..e1acc2556 100644
--- a/web/package.json
+++ b/web/package.json
@@ -17,6 +17,7 @@
"test": "echo 'No tests configured' && exit 0"
},
"dependencies": {
+ "@intlify/vite-plugin-vue-i18n": "^3.4.0",
"@kyvg/vue3-notification": "2.3.4",
"@meforma/vue-toaster": "1.2.2",
"ansi-to-html": "0.7.2",
@@ -29,6 +30,7 @@
"node-emoji": "1.11.0",
"pinia": "2.0.0",
"vue": "v3.2.20",
+ "vue-i18n": "9",
"vue-router": "4.0.10"
},
"devDependencies": {
diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json
new file mode 100644
index 000000000..260dea266
--- /dev/null
+++ b/web/src/assets/locales/en.json
@@ -0,0 +1,208 @@
+{
+ "login": "Login",
+ "welcome": "Welcome to Woodpecker",
+ "repos": "Repos",
+ "repositories": "Repositories",
+ "logout": "Logout",
+ "search": "Search...",
+ "username": "Username",
+ "password": "Password",
+ "url": "URL",
+
+ "not_found": {
+ "not_found": "Whoa 404, either we broke something or you had a typing mishap :-/",
+ "back_home": "Back to home"
+ },
+
+ "time": {
+ "tmpl": "MMM D, YYYY, HH:mm z",
+ "weeks_short": "w",
+ "days_short": "d",
+ "hours_short": "h",
+ "min_short": "min",
+ "sec_short": "sec",
+ "not_started": "not started yet"
+ },
+
+ "repo": {
+ "activity": "Activity",
+ "branches": "Branches",
+ "add": "Add repository",
+ "user_none": "This organization / user does not have any projects yet.",
+ "not_allowed": "Not allowed to access this repository",
+
+ "enable": {
+ "reload": "Reload repositories",
+ "enable": "Enable",
+ "enabled": "Already enabled",
+ "success": "Repository enabled",
+ "list_reloaded": "Repository list reloaded"
+ },
+
+ "settings": {
+ "settings": "Settings",
+
+ "general": {
+ "general": "General",
+ "project": "Project settings",
+ "save": "Save settings",
+ "success": "Repository settings updated",
+
+ "pipeline_path": {
+ "path": "Pipeline path",
+ "default": "By default: .woodpecker/*.yml -> .woodpecker.yml -> .drone.yml",
+ "desc": "Path to your pipeline config (for examplemy/path/). Folders should end with a/."
+ },
+
+ "allow_pr": {
+ "allow": "Allow Pull Requests",
+ "desc": "Pipelines can run on pull requests."
+ },
+
+ "protected": {
+ "protected": "Protected",
+ "desc": "Every pipeline needs to be approved before being executed."
+ },
+ "trusted": {
+ "trusted": "Trusted",
+ "desc": "Underlying pipeline containers get access to escalated capabilities like mounting volumes."
+ },
+ "visibility": {
+ "visibility": "Project visibility",
+
+ "public": {
+ "public": "Public",
+ "desc": "Every user can see your project without being logged in."
+ },
+ "private": {
+ "private": "Private",
+ "desc": "Only authenticated users of the Woodpecker instance can see this project."
+ },
+ "internal": {
+ "internal": "Internal",
+ "desc": "Only you and other owners of the repository can see this project."
+ }
+ },
+
+ "timeout": {
+ "timeout": "Timeout",
+ "minutes": "minutes"
+ },
+
+ "cancel_prev": {
+ "cancel": "Cancel previous pipelines",
+ "desc": "Enable to cancel running pipelines of the same event and context before starting the newly triggered one."
+ }
+ },
+
+ "secrets": {
+ "secrets": "Secrets",
+ "desc": "Secrets can be passed to individual pipeline steps at runtime as environmental variables.",
+ "none": "There are no secrets yet.",
+ "add": "Add secret",
+ "save": "Save secret",
+ "show": "Show secrets",
+ "name": "Name",
+ "value": "Value",
+ "deleted": "Secret deleted",
+ "created": "Secret created",
+ "saved": "Secret saved",
+
+ "images": {
+ "images": "Available for following images",
+ "desc": "Comma separated list of images where this secret is available, leave empty to allow all images"
+ },
+ "events": {
+ "events": "Available at following events",
+ "pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets."
+ }
+ },
+
+ "registries": {
+ "registries": "Registries",
+ "creds": "Registry credentials",
+ "desc": "Registries credentials can be added to use private images for your pipeline.",
+ "show": "Show registries",
+ "add": "Add registry",
+ "none": "There are no registry credentials yet.",
+ "save": "Save registry",
+ "created": "Registry credentials created",
+ "saved": "Registry credentials saved",
+ "deleted": "Registry credentials deleted",
+
+ "address": {
+ "address": "Address",
+ "placeholder": "Registry Address (e.g. docker.io)"
+ }
+ },
+
+ "badge": {
+ "badge": "Badge",
+ "url_branch": "URL for specific branch",
+ "markdown": "Markdown"
+ },
+
+ "actions": {
+ "actions": "Actions",
+
+ "repair": {
+ "repair": "Repair repository",
+ "success": "Repository repaired"
+ },
+ "disable": {
+ "disable": "Disable repository",
+ "success": "Repository disabled"
+ },
+ "delete": {
+ "delete": "Delete repository",
+ "confirm": "All data will be lost after this action!!!\n\nDo you really want to proceed?",
+ "success": "Repository deleted"
+ }
+ }
+ },
+
+ "build": {
+ "created": "Created",
+ "tasks": "Tasks",
+ "config": "Config",
+ "files": "Changed files ({0})",
+ "no_files": "No files have been changed.",
+ "execution_error": "Execution error",
+ "no_pipelines": "No pipelines have been started yet.",
+ "step_not_started": "This step hasn't started yet.",
+ "pipelines_for": "Pipelines for branch \"{0}\"",
+
+ "actions": {
+ "cancel": "Cancel",
+ "restart": "Restart",
+ "canceled": "This step has been canceled.",
+ "cancel_success": "Pipeline canceled",
+ "restart_success": "Pipeline restarted"
+ },
+ "protected": {
+ "awaits": "This pipeline is awaiting approval by some maintainer!",
+ "approve": "Approve",
+ "decline": "Decline",
+ "declined": "This pipeline has been declined!"
+ },
+ "event": {
+ "push": "Push",
+ "tag": "Tag",
+ "pr": "Pull Request",
+ "deploy": "Deploy"
+ }
+ }
+ },
+
+ "user": {
+ "oauth_error": "Error while authenticating against OAuth provider",
+ "internal_error": "Some internal error occurred",
+ "access_denied": "You are not allowed to login",
+ "token": "Your Personal Token",
+ "shell_setup": "Shell setup",
+ "api_usage": "Example API Usage",
+ "cli_usage": "Example CLI Usage",
+ "dl_cli": "Download CLI",
+ "shell_setup_before": "do shell setup steps before"
+ }
+}
diff --git a/web/src/components/build-feed/BuildFeedItem.vue b/web/src/components/build-feed/BuildFeedItem.vue
index c3a8ddbcf..c6795af26 100644
--- a/web/src/components/build-feed/BuildFeedItem.vue
+++ b/web/src/components/build-feed/BuildFeedItem.vue
@@ -9,7 +9,9 @@