mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +00:00
Lazy-load TimeAgo locales (#2094)
1. new translation docs 2. lazy-load TimeAgo locales (used for "x min ago" messages). This 1. reduces size and 2. provides all languages without adding them manually. 3. Remove DayJS locales, they're unused.
This commit is contained in:
parent
74a619ff5b
commit
d1c51f4af8
10 changed files with 67 additions and 38 deletions
|
@ -1,17 +1,9 @@
|
||||||
# Translations
|
# 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.
|
To translate the web UI into your language, we have [our own Weblate instance](https://translate.woodpecker-ci.org/). Please register there and translate Woodpecker into your language. **We won't accept PRs changing any language except English.**
|
||||||
Then, translate content of this file, but only the values:
|
|
||||||
|
|
||||||
```json
|
<a href="https://translate.woodpecker-ci.org/engage/woodpecker-ci/">
|
||||||
{
|
<img src="https://translate.woodpecker-ci.org/widgets/woodpecker-ci/-/ui/multi-blue.svg" alt="Translation status" />
|
||||||
"dont_translate": "Only translate this text"
|
</a>
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To add support for time formatting, import the language into two files:
|
Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
1
web/.gitignore
vendored
1
web/.gitignore
vendored
|
@ -3,3 +3,4 @@ node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
src/assets/timeAgoLocales
|
||||||
|
|
|
@ -140,11 +140,12 @@ import useApiClient from '~/compositions/useApiClient';
|
||||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { usePagination } from '~/compositions/usePaginate';
|
import { usePagination } from '~/compositions/usePaginate';
|
||||||
|
import useTimeAgo from '~/compositions/useTimeAgo';
|
||||||
import { Agent } from '~/lib/api/types';
|
import { Agent } from '~/lib/api/types';
|
||||||
import timeAgo from '~/utils/timeAgo';
|
|
||||||
|
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
const timeAgo = useTimeAgo();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const selectedAgent = ref<Partial<Agent>>();
|
const selectedAgent = ref<Partial<Agent>>();
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<span>{{ since }}</span>
|
<span>{{ since }}</span>
|
||||||
<template #popper
|
<template #popper
|
||||||
><span class="font-bold">{{ $t('created') }}</span> {{ created }}</template
|
><span class="font-bold">{{ $t('repo.pipeline.created') }}</span> {{ created }}</template
|
||||||
>
|
>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
import 'dayjs/locale/en';
|
|
||||||
import 'dayjs/locale/lv';
|
|
||||||
import 'dayjs/locale/de';
|
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { getUserLanguage } from '~/utils/locale';
|
|
||||||
|
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(advancedFormat);
|
dayjs.extend(advancedFormat);
|
||||||
dayjs.locale(getUserLanguage());
|
|
||||||
|
|
||||||
export function useDate() {
|
export function useDate() {
|
||||||
function toLocaleString(date: Date) {
|
function toLocaleString(date: Date) {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { getUserLanguage } from '~/utils/locale';
|
import { getUserLanguage } from '~/utils/locale';
|
||||||
|
|
||||||
|
import { loadTimeAgoLocale } from './useTimeAgo';
|
||||||
|
|
||||||
const userLanguage = getUserLanguage();
|
const userLanguage = getUserLanguage();
|
||||||
const fallbackLocale = 'en';
|
const fallbackLocale = 'en';
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
|
@ -17,6 +19,8 @@ export const loadLocaleMessages = async (locale: string) => {
|
||||||
|
|
||||||
i18n.global.setLocaleMessage(locale, messages);
|
i18n.global.setLocaleMessage(locale, messages);
|
||||||
|
|
||||||
|
loadTimeAgoLocale(locale);
|
||||||
|
|
||||||
return nextTick();
|
return nextTick();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { useElapsedTime } from '~/compositions/useElapsedTime';
|
||||||
import { Pipeline } from '~/lib/api/types';
|
import { Pipeline } from '~/lib/api/types';
|
||||||
import { prettyDuration } from '~/utils/duration';
|
import { prettyDuration } from '~/utils/duration';
|
||||||
import { convertEmojis } from '~/utils/emoji';
|
import { convertEmojis } from '~/utils/emoji';
|
||||||
import timeAgo from '~/utils/timeAgo';
|
|
||||||
|
import useTimeAgo from './useTimeAgo';
|
||||||
|
|
||||||
const { toLocaleString } = useDate();
|
const { toLocaleString } = useDate();
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export default (pipeline: Ref<Pipeline | undefined>) => {
|
||||||
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
|
const { time: sinceElapsed } = useElapsedTime(sinceUnderOneHour, sinceRaw);
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const timeAgo = useTimeAgo();
|
||||||
const since = computed(() => {
|
const since = computed(() => {
|
||||||
if (sinceRaw.value === 0) {
|
if (sinceRaw.value === 0) {
|
||||||
return i18n.t('time.not_started');
|
return i18n.t('time.not_started');
|
||||||
|
|
17
web/src/compositions/useTimeAgo.ts
Normal file
17
web/src/compositions/useTimeAgo.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import TimeAgo from 'javascript-time-ago';
|
||||||
|
import en from 'javascript-time-ago/locale/en.json';
|
||||||
|
|
||||||
|
import { getUserLanguage } from '~/utils/locale';
|
||||||
|
|
||||||
|
TimeAgo.addDefaultLocale(en);
|
||||||
|
|
||||||
|
const addedLocales = ['en'];
|
||||||
|
|
||||||
|
export default () => new TimeAgo(getUserLanguage());
|
||||||
|
export async function loadTimeAgoLocale(locale: string) {
|
||||||
|
if (!addedLocales.includes(locale)) {
|
||||||
|
const { default: timeAgoLocale } = await import(`~/assets/timeAgoLocales/${locale}.js`);
|
||||||
|
TimeAgo.addLocale(timeAgoLocale);
|
||||||
|
addedLocales.push(locale);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
import TimeAgo from 'javascript-time-ago';
|
|
||||||
import de from 'javascript-time-ago/locale/de.json';
|
|
||||||
import en from 'javascript-time-ago/locale/en.json';
|
|
||||||
import lv from 'javascript-time-ago/locale/lv.json';
|
|
||||||
|
|
||||||
import { getUserLanguage } from '~/utils/locale';
|
|
||||||
|
|
||||||
TimeAgo.addDefaultLocale(en);
|
|
||||||
TimeAgo.addLocale(de);
|
|
||||||
TimeAgo.addLocale(lv);
|
|
||||||
|
|
||||||
const timeAgo = new TimeAgo(getUserLanguage());
|
|
||||||
|
|
||||||
export default timeAgo;
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import { readdirSync } from 'fs';
|
import { copyFile, existsSync, mkdirSync, readdirSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
import Icons from 'unplugin-icons/vite';
|
import Icons from 'unplugin-icons/vite';
|
||||||
|
@ -38,6 +38,39 @@ export default defineConfig({
|
||||||
|
|
||||||
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
|
const filenames = readdirSync('src/assets/locales/').map((filename) => filename.replace('.json', ''));
|
||||||
|
|
||||||
|
if (!existsSync('src/assets/timeAgoLocales')) {
|
||||||
|
mkdirSync('src/assets/timeAgoLocales');
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames.forEach((name) => {
|
||||||
|
// copy timeAgo language
|
||||||
|
if (name === 'zh-Hans') {
|
||||||
|
// zh-Hans is called zh in javascript-time-ago, so we need to rename this
|
||||||
|
copyFile(
|
||||||
|
'node_modules/javascript-time-ago/locale/zh.json.js',
|
||||||
|
'src/assets/timeAgoLocales/zh-Hans.js',
|
||||||
|
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (name !== 'en') {
|
||||||
|
// English is always directly loaded (compiled by Vite) and thus not copied
|
||||||
|
copyFile(
|
||||||
|
`node_modules/javascript-time-ago/locale/${name}.json.js`,
|
||||||
|
`src/assets/timeAgoLocales/${name}.js`,
|
||||||
|
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'vue-i18n-supported-locales',
|
name: 'vue-i18n-supported-locales',
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
|
|
Loading…
Reference in a new issue