feat(web-stack): add new i18n guidelines + ESLint + update and fixes (#7753)

- Add new i18n guidelines
- Remove nested keys: `"foo": { "bar": "..." }` ➡️ `"foo.bar": "..."`
  This makes code cleaner (and as a side-effect, it now allows to use `foo` + `foo.bar` keys)
- Lite/6 shared translations are no longer prefixed with `core.`
- Duplicated keys across translation files are no longer accepted.
  This will prevent accidental overwrite of missing common translations
- `fr.json` and `en.json` now required to contains the same keys
- Raw text is no longer accepted in template to prevent forgetting a translation
  Raw text is still accepted in dev-related files (stories, dev pages...)
- Remove useless `alt` on `img`
- Display of percentage now uses `$n(value, 'percent')`
- Removed legacy `lite/src/i18n.ts`
- Add missing translations
- Added visual indicator for missing translations while in dev env: `🌎⟨i-am-missing⟩`
This commit is contained in:
Thierry Goettelmann
2024-06-20 09:35:36 +02:00
committed by GitHub
parent dd8ad3f274
commit 276d920cca
47 changed files with 737 additions and 518 deletions

View File

@@ -177,6 +177,59 @@ module.exports = {
'vue/valid-define-options': 'error',
},
},
// Specific rules for XO i18n-related files
{
files: [
'@xen-orchestra/lite/**/*.{vue,ts,json}',
'@xen-orchestra/web/**/*.{vue,ts,json}',
'@xen-orchestra/web-core/**/*.{vue,ts,json}',
],
extends: ['plugin:@intlify/vue-i18n/base'],
settings: {
'vue-i18n': {
localeDir: [
'@xen-orchestra/lite/src/locales/*.json',
'@xen-orchestra/web/src/locales/*.json',
'@xen-orchestra/web-core/lib/locales/*.json',
],
messageSyntaxVersion: '^9.9.0',
},
},
rules: {
strict: 'off',
'no-irregular-whitespace': 'off',
'@intlify/vue-i18n/no-deprecated-i18n-component': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-place-attr': 'error',
'@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'error',
'@intlify/vue-i18n/no-deprecated-modulo-syntax': 'error',
'@intlify/vue-i18n/no-deprecated-tc': 'error',
'@intlify/vue-i18n/no-html-messages': 'error',
'@intlify/vue-i18n/no-i18n-t-path-prop': 'error',
'@intlify/vue-i18n/no-missing-keys': 'error',
'@intlify/vue-i18n/no-raw-text': [
'error',
{
ignoreText: ['Xen Orchestra Lite', 'Xen Orchestra', 'XO Lite', 'XCP-ng', 'XO 5', '404', '⚠️'],
},
],
'@intlify/vue-i18n/no-v-html': 'error',
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
'@intlify/vue-i18n/no-missing-keys-in-other-locales': ['error', { ignoreLocales: ['de'] }],
},
},
// Specific rules for XO dev-related files
{
files: [
'@xen-orchestra/lite/src/stories/**/*',
'@xen-orchestra/lite/src/components/component-story/**/*',
'@xen-orchestra/web/src/pages/dev/**/*',
'@xen-orchestra/lite/src/views/story/**/*',
],
rules: {
'@intlify/vue-i18n/no-raw-text': 'off',
},
},
{
files: ['@xen-orchestra/{web-core,lite,web}/src/pages/**/*.vue'],
parserOptions: {

View File

@@ -11,7 +11,7 @@
{{ $t('send-us-feedback') }}
</MenuItem>
<MenuItem :icon="faArrowRightFromBracket" class="menu-item-logout" @click="logout">
{{ $t('core.log-out') }}
{{ $t('log-out') }}
</MenuItem>
</MenuList>
</template>

View File

@@ -7,7 +7,7 @@
<template v-else>
<FormWidget :before="currentFilterIcon">
<select v-model="newFilter.builder.property">
<option v-if="!newFilter.builder.property" value="">- {{ $t('property') }} -</option>
<option v-if="!newFilter.builder.property" value="">{{ `- ${$t('property')} -` }}</option>
<option v-for="(filter, property) in availableFilters" :key="property" :value="property">
{{ filter.label ?? property }}
</option>

View File

@@ -1,6 +1,6 @@
<template>
<div class="no-data">
<img alt="No data" class="img" src="@/assets/undraw-bug-fixing.svg" />
<img alt="" class="img" src="@/assets/undraw-bug-fixing.svg" />
<p class="text-error typo h4-medium">{{ $t('error-no-data') }}</p>
</div>
</template>

View File

@@ -11,7 +11,7 @@
rel="noopener noreferrer"
target="_blank"
>
{{ $t('here') }}
{{ `${$t('here')}` }}
</a>
</p>
</UiStatusPanel>

View File

@@ -8,7 +8,9 @@
class="progress-circle-fill"
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
/>
<text class="progress-circle-text" text-anchor="middle" x="50%" y="50%">{{ progress }}%</text>
<text class="progress-circle-text" text-anchor="middle" x="50%" y="50%">
{{ $n(progress / 100, 'percent') }}
</text>
</svg>
</template>

View File

@@ -10,7 +10,7 @@
class="progress-item"
>
<UiProgressBar :value="item.value" color="custom" />
<UiProgressLegend :label="item.label" :value="item.badgeLabel ?? `${item.value}%`" />
<UiProgressLegend :label="item.label" :value="item.badgeLabel ?? $n(item.value / 100, 'percent')" />
</div>
<slot :total-percent="computedData.totalPercentUsage" name="footer" />
</div>

View File

@@ -2,9 +2,9 @@
<FormInputGroup>
<FormNumber v-model="sizeInput" :max-decimals="3" />
<FormSelect v-model="prefixInput">
<option v-for="currentPrefix in availablePrefixes" :key="currentPrefix" :value="currentPrefix">
{{ currentPrefix }}B
</option>
<option value="Ki">{{ $t('bytes.ki') }}</option>
<option value="Mi">{{ $t('bytes.mi') }}</option>
<option value="Gi">{{ $t('bytes.gi') }}</option>
</FormSelect>
</FormInputGroup>
</template>

View File

@@ -3,7 +3,7 @@
<TreeItemLabel :icon="faServer" :route="{ name: 'host.dashboard', params: { uuid: host.uuid } }">
{{ host.name_label || '(Host)' }}
<template #addons>
<UiIcon v-if="isPoolMaster" v-tooltip="$t('core.master')" :icon="faStar" color="warning" />
<UiIcon v-if="isPoolMaster" v-tooltip="$t('master')" :icon="faStar" color="warning" />
<UiCounter v-if="isReady" v-tooltip="$t('vm-running', { count: vmCount })" :value="vmCount" color="info" />
</template>
</TreeItemLabel>

View File

@@ -17,7 +17,7 @@
<div v-else-if="isReady" :class="state" class="progress-item">
<UiProgressBar :max-value="maxValue" :value color="custom" />
<UiProgressScale :max-value="maxValue" :steps="1" unit="%" />
<UiProgressLegend :label="$t('vcpus')" :value="`${value}%`" />
<UiProgressLegend :label="$t('vcpus')" :value="$n(value / 100, 'percent')" />
<UiCardFooter class="ui-card-footer">
<template #left>
<p>{{ $t('vcpus-used') }}</p>

View File

@@ -10,7 +10,10 @@
{{ $t(`alarm-type.${alarm.type}`, { n: alarm.triggerLevel * 100 }) }}
</div>
</td>
<td class="level typo h7-semi-bold">{{ alarm.level * 100 }}%</td>
<td class="level typo h7-semi-bold">
<!-- Using `Math.min` because `alarm.level` can be `Infinity` -->
{{ $n(Math.min(alarm.level, 1), 'percent') }}
</td>
<td class="on">{{ $t('on-object', { object: alarm.cls }) }}</td>
<td class="object">
<ObjectLink :type="rawTypeToType(alarm.cls)" :uuid="alarm.obj_uuid" />

View File

@@ -27,7 +27,7 @@ import { useI18n } from 'vue-i18n'
const LinearChart = defineAsyncComponent(() => import('@/components/charts/LinearChart.vue'))
const { t } = useI18n()
const { t, n } = useI18n()
const hostLastWeekStats = inject(IK_HOST_LAST_WEEK_STATS)
@@ -88,5 +88,5 @@ const isStatFetched = computed(() => {
const isLoading = computed(() => isFetching.value || !isStatFetched.value)
const customValueFormatter: ValueFormatter = value => `${value}%`
const customValueFormatter: ValueFormatter = value => n(value / 100, 'percent')
</script>

View File

@@ -1,18 +1,18 @@
<template>
<div v-if="isDisplayed" class="summary typo c2-semi-bold">
<div class="summary-card">
<p>{{ $t('total-used') }}:</p>
<p>{{ $t('total-used:') }}</p>
<div class="summary-value">
<p>{{ percentUsed }}%</p>
<p>{{ $n(percentUsed / 100, 'percent') }}</p>
<p>
{{ formatSize(usage) }}
</p>
</div>
</div>
<div class="summary-card">
<p>{{ $t('total-free') }}:</p>
<p>{{ $t('total-free:') }}</p>
<div class="summary-value">
<p>{{ percentFree }}%</p>
<p>{{ $n(percentFree / 100, 'percent') }}</p>
<p>
{{ formatSize(free) }}
</p>

View File

@@ -17,7 +17,7 @@ const props = withDefaults(
{ color: 'info', maxValue: 100 }
)
const progressWithUnit = computed(() => {
const cssFillWidth = computed(() => {
const progress = (props.value / props.maxValue) * 100
return `${progress}%`
})
@@ -49,7 +49,7 @@ const progressWithUnit = computed(() => {
}
.fill {
width: v-bind(progressWithUnit);
width: v-bind(cssFillWidth);
height: var(--progress-bar-height, 0.4rem);
transition: width 1s ease-in-out;
background-color: var(--progress-bar-color);

View File

@@ -1,6 +1,6 @@
<template>
<p class="unit typo p3-regular">
<span>0{{ unit }}</span>
<span>{{ `0${unit}` }}</span>
<span v-for="step in steps" :key="step"> {{ Math.round((maxValue / (steps + 1)) * step) }}{{ unit }} </span>
<span>{{ maxValue }}{{ unit }}</span>
</p>

View File

@@ -1,158 +0,0 @@
import messages from '@intlify/unplugin-vue-i18n/messages'
import { createI18n } from 'vue-i18n'
interface Locales {
[key: string]: {
code: string
name: string
}
}
export const locales: Locales = {
en: {
code: 'en',
name: 'English',
},
fr: {
code: 'fr',
name: 'Français',
},
de: {
code: 'de',
name: 'Deutsch',
},
}
export default createI18n({
locale: localStorage.getItem('lang') ?? 'en',
fallbackLocale: 'en',
messages,
datetimeFormats: {
en: {
date_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
},
date_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
},
date_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
},
datetime_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
time: {
hour: '2-digit',
minute: '2-digit',
},
},
fr: {
date_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
},
date_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
},
date_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
},
datetime_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
time: {
hour: '2-digit',
minute: '2-digit',
},
},
de: {
date_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
},
date_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
},
date_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
},
datetime_short: {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_medium: {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
datetime_long: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
time: {
hour: '2-digit',
minute: '2-digit',
},
},
},
})

View File

@@ -8,19 +8,18 @@
"admin-login": "Administrator Login",
"admin-password": "Administrator Passwort",
"admin-password-confirm": "Administrator Passwortbestätigung",
"alarm-type": {
"cpu_usage": "CPU-Nutzung überschreitet {n}%",
"disk_usage": "Speicherbelegung überschreitet {n}%",
"fs_usage": "Dateisystem überschreitet {n}%",
"log_fs_usage": "Loggingdateisystem überschreitet {n}%",
"mem_usage": "Arbeitsspeicherauslastung überschreitet {n}%",
"memory_free_kib": "Freier Speicher fällt unter {n}%",
"network_usage": "Netzwerkauslastung überschreitet {n}%",
"physical_utilisation": "Physical utilisation exceeds {n}%",
"sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds {n}%",
"unknown": "unbekannter Alarmtyp"
},
"alarms": "Alarme",
"alarm-type.cpu_usage": "CPU-Nutzung überschreitet {n}%",
"alarm-type.disk_usage": "Speicherbelegung überschreitet {n}%",
"alarm-type.fs_usage": "Dateisystem überschreitet {n}%",
"alarm-type.log_fs_usage": "Loggingdateisystem überschreitet {n}%",
"alarm-type.mem_usage": "Arbeitsspeicherauslastung überschreitet {n}%",
"alarm-type.memory_free_kib": "Freier Speicher fällt unter {n}%",
"alarm-type.network_usage": "Netzwerkauslastung überschreitet {n}%",
"alarm-type.physical_utilisation": "Physical utilisation exceeds {n}%",
"alarm-type.sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds {n}%",
"alarm-type.unknown": "unbekannter Alarmtyp",
"all-good": "Alles in Ordnung!",
"allow-self-signed-ssl": "Es müssen eventuell selbstsignierte SSL-Zertifikate im Browser zugelassen werden",
"appearance": "Aussehen",
@@ -33,32 +32,29 @@
"check-errors": "Überprüfe die Fehler:",
"click-to-display-alarms": "Hier klicken um Alarme anzuzeigen:",
"click-to-return-default-pool": "Hier klicken um zum Standardpool zurückzukehren",
"coming-soon": "Bald verfügbar!",
"community": "Community",
"community-name": "{name} community",
"configuration": "Konfiguration",
"confirm-cancel": "Willst du wirklich abbrechen?",
"confirm-delete": "Willst du wirklich {0} löschen?",
"console": "Konsole",
"console-unavailable": "Die Konsole ist nicht verfügbar",
"copy": "Kopieren",
"cpu-provisioning": "Zugewiesene Prozessoren",
"cpu-provisioning-warning": "Die Anzahl der zugewiesenen vCPUs überschreitet die physikalisch vorhandenen CPUs. Die Systemperformance kann darunter leiden.",
"cpu-usage": "Prozessorauslastung",
"dashboard": "Dashboard",
"delete": "Löschen",
"delete-vms": "1 VM löschen| {n} VMs löschen",
"deploy": "Installieren",
"deploy-xoa": "XOA installieren",
"deploy-xoa-available-on-desktop": "Die Installation von XOA ist auf dem Desktop verfügbar",
"deploy-xoa-status": {
"configuring": "Konfiguriere XOA…",
"importing": "Importiere XOA…",
"not-responding": "XOA antwortet nicht",
"ready": "XOA ist startklar!",
"starting": "Starte XOA…",
"waiting": "Warte auf Antwort von XOA…"
},
"deploy-xoa-status.configuring": "Konfiguriere XOA…",
"deploy-xoa-status.importing": "Importiere XOA…",
"deploy-xoa-status.not-responding": "XOA antwortet nicht",
"deploy-xoa-status.ready": "XOA ist startklar!",
"deploy-xoa-status.starting": "Starte XOA…",
"deploy-xoa-status.waiting": "Warte auf Antwort von XOA…",
"descending": "absteigend",
"description": "Beschreibung",
"dhcp": "DHCP",
@@ -67,7 +63,6 @@
"dns": "DNS",
"do-you-have-needs": "Vermisst du Funktionen oder hast Vorschläge? Sag uns Bescheid: ",
"documentation": "Dokumentation",
"documentation-name": "{name} Dokumentation",
"edit-config": "Einstellungen anpassen",
"enabled": "Aktiviert",
"error-no-data": "Fehler beim Datenabruf.",
@@ -80,24 +75,22 @@
"export-vms": "VMs exportieren",
"export-vms-manually-information": "Some VM exports were not able to start automatically, probably due to your browser settings. To export them, you should click on each one. (Alternatively, copy the link as well.)",
"fetching-fresh-data": "Rufe neue Daten ab",
"filter": {
"comparison": {
"contains": "Enthält",
"ends-with": "Endet mit",
"equals": "Entspricht",
"is": "Ist",
"is-false": "Ist falsch",
"is-not": "Ist nicht",
"is-true": "Ist wahr",
"matches-regex": "Entspricht Regulärem Ausdruck",
"not-contain": "Enthält nicht",
"not-end-with": "Endet nicht mit",
"not-equal": "Entspricht nicht",
"not-match-regex": "Entspricht nicht Regulärem Ausdruck",
"not-start-with": "Beginnt nicht mit",
"starts-with": "Beginnt mit"
}
},
"filter.comparison.contains": "Enthält",
"filter.comparison.ends-with": "Endet mit",
"filter.comparison.equals": "Entspricht",
"filter.comparison.is": "Ist",
"filter.comparison.is-false": "Ist falsch",
"filter.comparison.is-not": "Ist nicht",
"filter.comparison.is-true": "Ist wahr",
"filter.comparison.matches-regex": "Entspricht Regulärem Ausdruck",
"filter.comparison.not-contain": "Enthält nicht",
"filter.comparison.not-end-with": "Endet nicht mit",
"filter.comparison.not-equal": "Entspricht nicht",
"filter.comparison.not-match-regex": "Entspricht nicht Regulärem Ausdruck",
"filter.comparison.not-start-with": "Beginnt nicht mit",
"filter.comparison.starts-with": "Beginnt mit",
"following-hosts-unreachable": "Die folgenden Hosts sind nicht erreichbar",
"force-reboot": "Neustart erzwingen",
"force-shutdown": "Herunterfahren erzwingen",
@@ -109,10 +102,10 @@
"go-back": "Zurück",
"gzip": "gzip",
"here": "Hier",
"host": {
"active": "Aktiv",
"inactive": "Inaktiv"
},
"host.active": "Aktiv",
"host.inactive": "Inaktiv",
"hosts": "Hosts",
"invalid-field": "Ungültiges Feld",
"keep-me-logged": "Angemeldet bleiben",
@@ -132,7 +125,6 @@
"n-vms": "1 VM | {n} VMs",
"name": "Name",
"netmask": "Netzmaske",
"network": "Netzwerk",
"network-download": "Download",
"network-throughput": "Netzwerkdurchsatz",
"network-upload": "Upload",
@@ -159,23 +151,22 @@
"please-confirm": "Bitte bestätigen",
"pool-cpu-usage": "Pool Prozessorauslastung",
"pool-ram-usage": "Pool Arbeitsspeicherbelegung",
"power-on-for-console": "Konsole ist nach Start der VM verfügbar",
"power-state": "Energiezustand",
"property": "Feld",
"ram-usage": "Arbeitsspeicherbelegung",
"reboot": "Neustart",
"reformat": "Reformat",
"relative-time": {
"day": "1 Tag | {n} Tage",
"future": "In {str}",
"hour": "1 Stunde | {n} Stunden",
"minute": "1 Minute | {n} Minuten",
"month": "1 Monat | {n} Monate",
"now": "Gerade eben",
"past": "Vor {str}",
"second": "1 Sekunde | {n} Sekunden",
"year": "1 Jahr | {n} Jahre"
},
"relative-time.day": "1 Tag | {n} Tage",
"relative-time.future": "In {str}",
"relative-time.hour": "1 Stunde | {n} Stunden",
"relative-time.minute": "1 Minute | {n} Minuten",
"relative-time.month": "1 Monat | {n} Monate",
"relative-time.now": "Gerade eben",
"relative-time.past": "Vor {str}",
"relative-time.second": "1 Sekunde | {n} Sekunden",
"relative-time.year": "1 Jahr | {n} Jahre",
"resume": "Fortsetzen",
"save": "Speichern",
"select-compression": "Kompression auswählen",
@@ -183,10 +174,10 @@
"selected-vms-in-execution": "Einige der ausgewählten VMs sind eingeschaltet",
"send-ctrl-alt-del": "Ctrl+Alt+Del senden",
"send-us-feedback": "Gib uns Feedback",
"select": {
"network": "Ein Netzwerk auswählen",
"storage": "Einen Speicher auswählen"
},
"select.network": "Ein Netzwerk auswählen",
"select.storage": "Einen Speicher auswählen",
"settings": "Einstellungen",
"shutdown": "Herunterfahren",
"snapshot": "Snapshot",
@@ -200,39 +191,33 @@
"start": "Starten",
"start-on-host": "Auf bestimmtem Host starten",
"static-ip": "Statische IP",
"stats": "Statistiken",
"status": "Status",
"storage": "Speicher",
"storage-usage": "Belegung Storage",
"support": "Support",
"support-name": "{name} pro support",
"suspend": "Standby",
"switch-theme": "Theme wechseln",
"system": "System",
"task": {
"estimated-end": "Vorraussichtliches Ende",
"progress": "Fortschritt",
"started": "Beginn"
},
"tasks": "Aufgaben",
"task.estimated-end": "Vorraussichtliches Ende",
"task.progress": "Fortschritt",
"task.started": "Beginn",
"theme-auto": "Auto",
"theme-dark": "Dunkel",
"theme-light": "Hell",
"this-vm-cant-be-migrated": "Diese VM kann nicht migriert werden",
"top-#": "Top {n}",
"total": "Gesamt",
"total-cpus": "Gesamt CPUs",
"total-free": "Gesamt frei",
"total-used": "Gesamt benutzt",
"total-free:": "Gesamt frei:",
"total-used:": "Gesamt benutzt:",
"unreachable-hosts": "Hosts nicht erreichbar",
"unreachable-hosts-reload-page": "Abgeschlossen, bitte lade die Seite neu",
"vcpus": "vCPUs",
"vcpus-used": "vCPUs in Benutzung",
"version": "Version",
"vm": {
"active": "Aktiv",
"inactive": "Inaktiv"
},
"vm.active": "Aktiv",
"vm.inactive": "Inaktiv",
"vm-is-running": "Die VM ist eingeschalten",
"vm-running": "VM eingeschalten | VMs eingeschalten",
"vms": "VMs",

View File

@@ -8,19 +8,18 @@
"admin-login": "Admin login",
"admin-password": "Admin password",
"admin-password-confirm": "Confirm admin password",
"alarm-type": {
"cpu_usage": "CPU usage exceeds {n}%",
"disk_usage": "Disk usage exceeds {n}%",
"fs_usage": "FS usage exceeds {n}%",
"log_fs_usage": "Log FS usage exceeds {n}%",
"mem_usage": "Memory usage exceeds {n}%",
"memory_free_kib": "Free memory falls below {n}%",
"network_usage": "Network usage exceeds {n}%",
"physical_utilisation": "Physical utilisation exceeds {n}%",
"sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds {n}%",
"unknown": "Unknown alarm type"
},
"alarms": "Alarms",
"alarm-type.cpu_usage": "CPU usage exceeds {n}%",
"alarm-type.disk_usage": "Disk usage exceeds {n}%",
"alarm-type.fs_usage": "FS usage exceeds {n}%",
"alarm-type.log_fs_usage": "Log FS usage exceeds {n}%",
"alarm-type.mem_usage": "Memory usage exceeds {n}%",
"alarm-type.memory_free_kib": "Free memory falls below {n}%",
"alarm-type.network_usage": "Network usage exceeds {n}%",
"alarm-type.physical_utilisation": "Physical utilisation exceeds {n}%",
"alarm-type.sr_io_throughput_total_per_host": "SR IO throughput total per host exceeds {n}%",
"alarm-type.unknown": "Unknown alarm type",
"all-good": "All good!",
"allow-self-signed-ssl": "You may need to allow self-signed SSL certificates in your browser",
"appearance": "Appearance",
@@ -34,32 +33,29 @@
"click-to-display-alarms": "Click to display alarms:",
"click-to-return-default-pool": "Click here to return to the default pool",
"close": "Close",
"coming-soon": "Coming soon!",
"community": "Community",
"community-name": "{name} community",
"configuration": "Configuration",
"confirm-cancel": "Are you sure you want to cancel?",
"confirm-delete": "You're about to delete {0}",
"console": "Console",
"console-unavailable": "Console unavailable",
"copy": "Copy",
"cpu-provisioning": "CPU provisioning",
"cpu-provisioning-warning": "The number of vCPUs allocated exceeds the number of physical CPUs available. System performance could be affected",
"cpu-usage": "CPU usage",
"dashboard": "Dashboard",
"delete": "Delete",
"delete-vms": "Delete 1 VM | Delete {n} VMs",
"deploy": "Deploy",
"deploy-xoa": "Deploy XOA",
"deploy-xoa-available-on-desktop": "XOA deployment is available on your desktop interface",
"deploy-xoa-status": {
"configuring": "Configuring XOA…",
"importing": "Importing XOA…",
"not-responding": "XOA is not responding",
"ready": "XOA is ready!",
"starting": "Starting XOA…",
"waiting": "Waiting for XOA to respond…"
},
"deploy-xoa-status.configuring": "Configuring XOA…",
"deploy-xoa-status.importing": "Importing XOA…",
"deploy-xoa-status.not-responding": "XOA is not responding",
"deploy-xoa-status.ready": "XOA is ready!",
"deploy-xoa-status.starting": "Starting XOA…",
"deploy-xoa-status.waiting": "Waiting for XOA to respond…",
"descending": "descending",
"description": "Description",
"dhcp": "DHCP",
@@ -68,7 +64,6 @@
"dns": "DNS",
"do-you-have-needs": "You have needs and/or expectations? Let us know",
"documentation": "Documentation",
"documentation-name": "{name} documentation",
"edit-config": "Edit config",
"enabled": "Enabled",
"error-no-data": "Error, can't collect data.",
@@ -81,24 +76,22 @@
"export-vms": "Export VMs",
"export-vms-manually-information": "Some VM exports were not able to start automatically, probably due to your browser settings. To export them, you should click on each one. (Alternatively, copy the link as well.)",
"fetching-fresh-data": "Fetching fresh data",
"filter": {
"comparison": {
"contains": "Contains",
"ends-with": "Ends with",
"equals": "Equals",
"is": "Is",
"is-false": "Is false",
"is-not": "Is not",
"is-true": "Is true",
"matches-regex": "Matches regex",
"not-contain": "Doesn't contain",
"not-end-with": "Doesn't end with",
"not-equal": "Doesn't equal",
"not-match-regex": "Doesn't match regex",
"not-start-with": "Doesn't start with",
"starts-with": "Starts with"
}
},
"filter.comparison.contains": "Contains",
"filter.comparison.ends-with": "Ends with",
"filter.comparison.equals": "Equals",
"filter.comparison.is": "Is",
"filter.comparison.is-false": "Is false",
"filter.comparison.is-not": "Is not",
"filter.comparison.is-true": "Is true",
"filter.comparison.matches-regex": "Matches regex",
"filter.comparison.not-contain": "Doesn't contain",
"filter.comparison.not-end-with": "Doesn't end with",
"filter.comparison.not-equal": "Doesn't equal",
"filter.comparison.not-match-regex": "Doesn't match regex",
"filter.comparison.not-start-with": "Doesn't start with",
"filter.comparison.starts-with": "Starts with",
"following-hosts-unreachable": "The following hosts are unreachable",
"force-reboot": "Force reboot",
"force-shutdown": "Force shutdown",
@@ -110,10 +103,10 @@
"go-back": "Go back",
"gzip": "gzip",
"here": "Here",
"host": {
"active": "Active",
"inactive": "Inactive"
},
"host.active": "Active",
"host.inactive": "Inactive",
"hosts": "Hosts",
"invalid-field": "Invalid field",
"keep-me-logged": "Keep me logged in",
@@ -133,7 +126,6 @@
"n-vms": "1 VM | {n} VMs",
"name": "Name",
"netmask": "Netmask",
"network": "Network",
"network-download": "Download",
"network-throughput": "Network throughput",
"network-upload": "Upload",
@@ -160,23 +152,22 @@
"please-confirm": "Please confirm",
"pool-cpu-usage": "Pool CPU Usage",
"pool-ram-usage": "Pool RAM Usage",
"power-on-for-console": "Power on your VM to access its console",
"power-state": "Power state",
"property": "Property",
"ram-usage": "RAM usage",
"reboot": "Reboot",
"reformat": "Reformat",
"relative-time": {
"day": "1 day | {n} days",
"future": "In {str}",
"hour": "1 hour | {n} hours",
"minute": "1 minute | {n} minutes",
"month": "1 month | {n} months",
"now": "Just now",
"past": "{str} ago",
"second": "1 second | {n} seconds",
"year": "1 year | {n} years"
},
"relative-time.day": "1 day | {n} days",
"relative-time.future": "In {str}",
"relative-time.hour": "1 hour | {n} hours",
"relative-time.minute": "1 minute | {n} minutes",
"relative-time.month": "1 month | {n} months",
"relative-time.now": "Just now",
"relative-time.past": "{str} ago",
"relative-time.second": "1 second | {n} seconds",
"relative-time.year": "1 year | {n} years",
"resume": "Resume",
"save": "Save",
"select-compression": "Select a compression",
@@ -184,10 +175,10 @@
"selected-vms-in-execution": "Some selected VMs are running",
"send-ctrl-alt-del": "Send Ctrl+Alt+Del",
"send-us-feedback": "Send us feedback",
"select": {
"network": "Select a network",
"storage": "Select a storage"
},
"select.network": "Select a network",
"select.storage": "Select a storage",
"settings": "Settings",
"shutdown": "Shutdown",
"snapshot": "Snapshot",
@@ -201,21 +192,16 @@
"start": "Start",
"start-on-host": "Start on specific host",
"static-ip": "Static IP",
"stats": "Stats",
"status": "Status",
"storage": "Storage",
"storage-usage": "Storage usage",
"support": "Support",
"support-name": "{name} pro support",
"suspend": "Suspend",
"switch-theme": "Switch theme",
"system": "System",
"task": {
"estimated-end": "Estimated end",
"progress": "Progress",
"started": "Started"
},
"tasks": "Tasks",
"task.estimated-end": "Estimated end",
"task.progress": "Progress",
"task.started": "Started",
"theme-auto": "Auto",
"theme-dark": "Dark",
"theme-light": "Light",
@@ -223,17 +209,17 @@
"top-#": "Top {n}",
"total": "Total",
"total-cpus": "Total CPUs",
"total-free": "Total free",
"total-used": "Total used",
"total-free:": "Total free:",
"total-used:": "Total used:",
"unreachable-hosts": "Unreachable hosts",
"unreachable-hosts-reload-page": "Done, reload the page",
"vcpus": "vCPUs",
"vcpus-used": "vCPUs used",
"version": "Version",
"vm": {
"active": "Active",
"inactive": "Inactive"
},
"vm.active": "Active",
"vm.inactive": "Inactive",
"vm-is-running": "The VM is running",
"vm-running": "VM running | VMs running",
"vms": "VMs",

View File

@@ -8,19 +8,18 @@
"admin-login": "Nom d'utilisateur administrateur",
"admin-password": "Mot de passe administrateur",
"admin-password-confirm": "Confirmer le mot de passe administrateur",
"alarm-type": {
"cpu_usage": "L'utilisation du CPU dépasse {n}%",
"disk_usage": "L'utilisation du disque dépasse {n}%",
"fs_usage": "L'utilisation de FS dépasse {n}%",
"log_fs_usage": "L'utilisation de Log FS dépasse {n}%",
"mem_usage": "L'utilisation de la mémoire dépasse {n}%",
"memory_free_kib": "La mémoire libre est inférieure à {n}%",
"network_usage": "L'utilisation du réseau dépasse {n}%",
"physical_utilisation": "L'utilisation physique dépasse {n}%",
"sr_io_throughput_total_per_host": "Le débit total de l'IO SR par hôte dépasse {n}%",
"unknown": "Type d'alarme inconnu"
},
"alarms": "Alarmes",
"alarm-type.cpu_usage": "L'utilisation du CPU dépasse {n}%",
"alarm-type.disk_usage": "L'utilisation du disque dépasse {n}%",
"alarm-type.fs_usage": "L'utilisation de FS dépasse {n}%",
"alarm-type.log_fs_usage": "L'utilisation de Log FS dépasse {n}%",
"alarm-type.mem_usage": "L'utilisation de la mémoire dépasse {n}%",
"alarm-type.memory_free_kib": "La mémoire libre est inférieure à {n}%",
"alarm-type.network_usage": "L'utilisation du réseau dépasse {n}%",
"alarm-type.physical_utilisation": "L'utilisation physique dépasse {n}%",
"alarm-type.sr_io_throughput_total_per_host": "Le débit total de l'IO SR par hôte dépasse {n}%",
"alarm-type.unknown": "Type d'alarme inconnu",
"all-good": "Tout va bien !",
"allow-self-signed-ssl": "Vous devrez peut-être autoriser les certificats SSL auto-signés depuis votre navigateur",
"appearance": "Apparence",
@@ -34,32 +33,29 @@
"click-to-display-alarms": "Cliquer pour afficher les alarmes :",
"click-to-return-default-pool": "Cliquer ici pour revenir au pool par défaut",
"close": "Fermer",
"coming-soon": "Bientôt disponible !",
"community": "Communauté",
"community-name": "Communauté {name}",
"configuration": "Configuration",
"confirm-cancel": "Êtes-vous sûr de vouloir annuler ?",
"confirm-delete": "Vous êtes sur le point de supprimer {0}",
"console": "Console",
"console-unavailable": "Console indisponible",
"copy": "Copier",
"cpu-provisioning": "Provisionnement CPU",
"cpu-provisioning-warning": "Le nombre de vCPU alloués dépasse le nombre de CPU physique disponible. Les performances du système pourraient être affectées",
"cpu-usage": "Utilisation CPU",
"dashboard": "Tableau de bord",
"delete": "Supprimer",
"delete-vms": "Supprimer 1 VM | Supprimer {n} VMs",
"deploy": "Déployer",
"deploy-xoa": "Déployer XOA",
"deploy-xoa-available-on-desktop": "Le déploiement de la XOA est disponible sur ordinateur",
"deploy-xoa-status": {
"configuring": "Configuration de la XOA…",
"importing": "Importation de la XOA…",
"not-responding": "La XOA ne répond pas",
"ready": "La XOA est prête !",
"starting": "Démarrage de la XOA…",
"waiting": "En attente de réponse de la XOA…"
},
"deploy-xoa-status.configuring": "Configuration de la XOA…",
"deploy-xoa-status.importing": "Importation de la XOA…",
"deploy-xoa-status.not-responding": "La XOA ne répond pas",
"deploy-xoa-status.ready": "La XOA est prête !",
"deploy-xoa-status.starting": "Démarrage de la XOA…",
"deploy-xoa-status.waiting": "En attente de réponse de la XOA…",
"descending": "descendant",
"description": "Description",
"dhcp": "DHCP",
@@ -68,7 +64,6 @@
"display": "Affichage",
"do-you-have-needs": "Vous avez des besoins et/ou des attentes ? Faites le nous savoir",
"documentation": "Documentation",
"documentation-name": "Documentation {name}",
"edit-config": "Modifier config",
"enabled": "Activé",
"error-no-data": "Erreur, impossible de collecter les données.",
@@ -81,24 +76,22 @@
"export-vms": "Exporter les VMs",
"export-vms-manually-information": "Certaines exportations de VMs n'ont pas pu démarrer automatiquement, peut-être en raison des paramètres du navigateur. Pour les exporter, vous devrez cliquer sur chacune d'entre elles. (Ou copier le lien.)",
"fetching-fresh-data": "Récupération de données à jour",
"filter": {
"comparison": {
"contains": "Contient",
"ends-with": "Termine par",
"equals": "Est égal à",
"is": "Est",
"is-false": "Est faux",
"is-not": "N'est pas",
"is-true": "Est vrai",
"matches-regex": "Correspond à la regex",
"not-contain": "Ne contient pas",
"not-end-with": "Ne termine pas par",
"not-equal": "N'est pas égal à",
"not-match-regex": "Ne correspond pas à la regex",
"not-start-with": "Ne commence pas par",
"starts-with": "Commence par"
}
},
"filter.comparison.contains": "Contient",
"filter.comparison.ends-with": "Termine par",
"filter.comparison.equals": "Est égal à",
"filter.comparison.is": "Est",
"filter.comparison.is-false": "Est faux",
"filter.comparison.is-not": "N'est pas",
"filter.comparison.is-true": "Est vrai",
"filter.comparison.matches-regex": "Correspond à la regex",
"filter.comparison.not-contain": "Ne contient pas",
"filter.comparison.not-end-with": "Ne termine pas par",
"filter.comparison.not-equal": "N'est pas égal à",
"filter.comparison.not-match-regex": "Ne correspond pas à la regex",
"filter.comparison.not-start-with": "Ne commence pas par",
"filter.comparison.starts-with": "Commence par",
"following-hosts-unreachable": "Les hôtes suivants sont inaccessibles",
"force-reboot": "Forcer le redémarrage",
"force-shutdown": "Forcer l'arrêt",
@@ -110,10 +103,10 @@
"go-back": "Revenir en arrière",
"gzip": "gzip",
"here": "Ici",
"host": {
"active": "Actif | Actif | Actifs",
"inactive": "Inactif | Inactif | Inactifs"
},
"host.active": "Actif | Actif | Actifs",
"host.inactive": "Inactif | Inactif | Inactifs",
"hosts": "Hôtes",
"invalid-field": "Champ invalide",
"keep-me-logged": "Rester connecté",
@@ -133,7 +126,6 @@
"n-vms": "1 VM | {n} VMs",
"name": "Nom",
"netmask": "Masque réseau",
"network": "Réseau",
"network-download": "Descendant",
"network-throughput": "Débit du réseau",
"network-upload": "Montant",
@@ -160,23 +152,22 @@
"please-confirm": "Veuillez confirmer",
"pool-cpu-usage": "Utilisation CPU du Pool",
"pool-ram-usage": "Utilisation RAM du Pool",
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
"power-state": "État d'alimentation",
"property": "Propriété",
"ram-usage": "Utilisation de la RAM",
"reboot": "Redémarrer",
"reformat": "Reformater",
"relative-time": {
"day": "1 jour | {n} jours",
"future": "Dans {str}",
"hour": "1 heure | {n} heures",
"minute": "1 minute | {n} minutes",
"month": "{n} mois",
"now": "À l'instant",
"past": "Il y a {str}",
"second": "1 seconde | {n} secondes",
"year": "1 an | {n} ans"
},
"relative-time.day": "1 jour | {n} jours",
"relative-time.future": "Dans {str}",
"relative-time.hour": "1 heure | {n} heures",
"relative-time.minute": "1 minute | {n} minutes",
"relative-time.month": "{n} mois",
"relative-time.now": "À l'instant",
"relative-time.past": "Il y a {str}",
"relative-time.second": "1 seconde | {n} secondes",
"relative-time.year": "1 an | {n} ans",
"resume": "Reprendre",
"save": "Enregistrer",
"select-compression": "Sélectionnez une compression",
@@ -184,10 +175,10 @@
"selected-vms-in-execution": "Certaines VMs sélectionnées sont en cours d'exécution",
"send-ctrl-alt-del": "Envoyer Ctrl+Alt+Suppr",
"send-us-feedback": "Envoyez-nous vos commentaires",
"select": {
"network": "Sélectionner un réseau",
"storage": "Sélectionner un SR"
},
"select.network": "Sélectionner un réseau",
"select.storage": "Sélectionner un SR",
"settings": "Paramètres",
"shutdown": "Arrêter",
"snapshot": "Instantané",
@@ -201,21 +192,16 @@
"start": "Démarrer",
"start-on-host": "Démarrer sur un hôte spécifique",
"static-ip": "IP statique",
"stats": "Stats",
"status": "Statut",
"storage": "Stockage",
"storage-usage": "Utilisation du stockage",
"support": "Support",
"support-name": "Support pro {name}",
"suspend": "Suspendre",
"switch-theme": "Changer de thème",
"system": "Système",
"task": {
"estimated-end": "Fin estimée",
"progress": "Progression",
"started": "Démarré"
},
"tasks": "Tâches",
"task.estimated-end": "Fin estimée",
"task.progress": "Progression",
"task.started": "Démarré",
"theme-auto": "Auto",
"theme-dark": "Sombre",
"theme-light": "Clair",
@@ -223,17 +209,17 @@
"top-#": "Top {n}",
"total": "Total",
"total-cpus": "Total CPUs",
"total-free": "Total libre",
"total-used": "Total utilisé",
"total-free:": "Total libre:",
"total-used:": "Total utilisé:",
"unreachable-hosts": "Hôtes inaccessibles",
"unreachable-hosts-reload-page": "C'est fait. Rafraîchir la page",
"vcpus": "vCPUs",
"vcpus-used": "vCPUs utilisés",
"version": "Version",
"vm": {
"active": "Active | Active | Actives",
"inactive": "Inactive | Inactive | Inactives"
},
"vm.active": "Active | Active | Actives",
"vm.inactive": "Inactive | Inactive | Inactives",
"vm-is-running": "La VM est en cours d'exécution",
"vm-running": "VM en cours d'exécution | VMs en cours d'exécution",
"vms": "VMs",

View File

@@ -1,4 +1,4 @@
<template>Chargement en cours...</template>
<template>{{ $t('loading-in-progress') }}</template>
<script lang="ts" setup>
import { usePoolStore } from '@/stores/xen-api/pool.store'

View File

@@ -1,6 +1,6 @@
<template>
<div>
<img alt="Not found" src="../assets/object-not-found.svg" />
<img alt="" src="../assets/object-not-found.svg" />
<p class="text">{{ $t('object-not-found', { id }) }}</p>
<UiButton @click="router.push({ name: 'home' })">{{ $t('back-pool-dashboard') }}</UiButton>
</div>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<img alt="Not found" src="../assets/page-not-found.svg" />
<img alt="" src="../assets/page-not-found.svg" />
<p class="numeric">404</p>
<p class="text">{{ $t('page-not-found') }}</p>
<UiButton @click="router.push({ name: 'home' })">{{ $t('back-pool-dashboard') }}</UiButton>

View File

@@ -1,7 +1,7 @@
<template>
<UiCard class="pool-vms-view">
<UiCardTitle subtitle>
VMs
{{ $t('vms') }}
<template v-if="isMobile" #right>
<VmsActionsBar :selected-refs="selectedVmsRefs" />
</template>

View File

@@ -10,8 +10,8 @@
<UiKeyValueRow>
<template #key>{{ $t('version') }}</template>
<template #value>
v{{ xoLiteVersion }}
<code v-if="xoLiteGitHead"> ({{ xoLiteGitHead.slice(0, 5) }}) </code>
{{ `v${xoLiteVersion}` }}
<code v-if="xoLiteGitHead">{{ `(${xoLiteGitHead.slice(0, 5)})` }}</code>
</template>
</UiKeyValueRow>
<UiKeyValueRow>
@@ -37,7 +37,7 @@
<UiKeyValueList>
<UiKeyValueRow>
<template #key>{{ $t('version') }}</template>
<template #value>v{{ xcpVersion }}</template>
<template #value>{{ `v${xcpVersion}` }}</template>
</UiKeyValueRow>
<UiKeyValueRow>
<template #key>{{ $t('news') }}</template>
@@ -85,9 +85,13 @@
:class="{ selected: colorMode === colorModeOption }"
@click="colorMode = colorModeOption"
>
<img v-if="colorModeOption === 'light'" src="@/assets/color-mode-light.svg" alt="Color mode light" />
<img v-else-if="colorModeOption === 'dark'" src="@/assets/color-mode-dark.svg" alt="Color mode dark" />
<img v-else src="@/assets/color-mode-auto.svg" alt="Color mode auto" />
<img v-if="colorModeOption === 'light'" src="@/assets/color-mode-light.svg" :alt="$t('dark-mode.disable')" />
<img
v-else-if="colorModeOption === 'dark'"
src="@/assets/color-mode-dark.svg"
:alt="$t('dark-mode.enable')"
/>
<img v-else src="@/assets/color-mode-auto.svg" :alt="$t('dark-mode.auto')" />
<span>
<UiIcon v-if="colorMode === colorModeOption" :icon="faCheck" />
{{ $t(`theme-${colorModeOption}`) }}
@@ -114,10 +118,10 @@ import UiCard from '@/components/ui/UiCard.vue'
import UiCardTitle from '@/components/ui/UiCardTitle.vue'
import UiKeyValueList from '@/components/ui/UiKeyValueList.vue'
import UiKeyValueRow from '@/components/ui/UiKeyValueRow.vue'
import { locales } from '@/i18n'
import { usePageTitleStore } from '@/stores/page-title.store'
import { useHostStore } from '@/stores/xen-api/host.store'
import { usePoolStore } from '@/stores/xen-api/pool.store'
import { locales } from '@core/i18n'
import { useUiStore } from '@core/stores/ui.store'
import { faEarthAmericas, faGear, faCheck } from '@fortawesome/free-solid-svg-icons'
import type { BasicColorSchema } from '@vueuse/core'

View File

@@ -66,13 +66,13 @@
:value="sr"
:class="sr.physical_size - sr.physical_utilisation < REQUIRED_GB * 1024 ** 3 ? 'warning' : 'success'"
>
{{ sr.name_label }} -
{{ `${sr.name_label} -` }}
{{
$t('n-gb-left', {
n: Math.round((sr.physical_size - sr.physical_utilisation) / 1024 ** 3),
})
}}
<span v-if="sr.physical_size - sr.physical_utilisation < REQUIRED_GB * 1024 ** 3"></span>
<template v-if="sr.physical_size - sr.physical_utilisation < REQUIRED_GB * 1024 ** 3"></template>
</option>
</FormSelect>
</FormInputWrapper>

View File

@@ -0,0 +1,162 @@
# Internationalization (i18n)
Internationalization of the app is done with [Vue-i18n](https://vue-i18n.intlify.dev/).
Sources of truth are `en.json` files in different projects.
## Translation files to use
| Target | Path | Notes |
| ------------------------------- | ----------------------------- | --------------------- |
| Shared between XO Lite and XO 6 | `web-core/lib/locales/*.json` | |
| Specific to XO Lite | `lite/src/locales/*.json` | |
| Specific to XO 6 | `web/src/locales/*.json` | |
| Specific to XO Core | `web-core/lib/locales/*.json` | Prefixed with `core.` |
## Translation keys
Translation keys MUST be in English and SHOULD use kebab-case.
Translation keys MUST NOT be nested. To group keys, use `prefix.key: value` instead of `prefix: { key: value }`.
Translation keys MUST be sorted alphabetically.
As a bonus, this will allow the usage of `foo` and `foo.bar` as separate keys.
Separate groups with a blank line.
❌ BAD
```json
{
"bonjour": "Hello",
"foo_bar": "Foo Bar",
"a": "A",
"b": {
"foo": "B Foo",
"bar": "B Bar",
"baz": "B Baz"
},
"c": "C"
}
```
✅ GOOD
```json
{
"a": "A",
"b.bar": "B Bar",
"b.baz": "B Baz",
"b.foo": "B Foo",
"c": "C",
"foo-bar": "Foo Bar",
"hello": "Hello"
}
```
## Punctuation
Strings punctuations should be included in the translation key.
This allows better adapting the punctuation to the rules of the target language.
For example:
- in English, interrogative sentences end with a question mark with no space before it
- in French, interrogative sentences end with a question mark with a space before it
- in Spanish, interrogative sentences start with an inverted question mark and end with a question mark with no spaces
> [!IMPORTANT]
> Since HTML is not accepted in translations, you must use a non-breaking space character (``) instead of `&nbsp;`
### Example:
#### English
```json
{
"confirm": "Confirm",
"confirm?": "Confirm?",
"warning": "Warning",
"warning!:": "Warning!"
}
```
#### French
```json
{
"confirm": "Confirmer",
"confirm?": "Confirmer?",
"warning": "Avertissement",
"warning!:": "Attention!"
}
```
#### Spanish
```json
{
"confirm": "Confirmar",
"confirm?": "¿Confirmar?",
"warning": "Advertencia",
"warning!:": "¡Atención!"
}
```
## Percentages
To include a percentage in a translation, use `$n(value, 'percent')`.
The value will be rounded to two decimal places maximum.
## Numbers
To include a number in a translation, use `$n(value)`.
## Dates and times
To include a date or a time in a translation, use `$d(value, '<format>')`.
### Example
```html
<p>{{ $d(date, "date_short") }}</p>
<!-- 9/10/2022 -->
<p>{{ $d(date, "date_medium") }}</p>
<!-- Sep 10, 2022 -->
<p>{{ $d(date, "date_long") }}</p>
<!-- September 10, 2022 -->
<p>{{ $d(date, "datetime_short") }}</p>
<!-- 9/10/2022, 06:30 PM -->
<p>{{ $d(date, "datetime_medium") }}</p>
<!-- Sep 10, 2022, 06:30 PM -->
<p>{{ $d(date, "datetime_long") }}</p>
<!-- September 10, 2022 at 06:30 PM -->
<p>{{ $d(date, "time") }}</p>
<!-- 06:30 PM -->
```
## Literal strings
Literal strings are not allowed in Vue templates.
If you really need to **exceptionally** include a literal string, you can either:
- use the mustache syntax with concatenation
```html
<!-- ❌ BAD -->
<div>v{{ version }}</div>
<!-- ✅ GOOD -->
<div>{{ `v${version}` }}</div>
```
- add the string as an exception in `.eslintrc.js` for the `@intlify/vue-i18n/no-raw-text` rule
> [!TIP]
> In most cases, literal strings will not be necessary. Think twice before using them.

View File

@@ -1 +1,2 @@
/// <reference types="novnc__novnc" />
/// <reference types="vite/client" />

View File

@@ -3,7 +3,9 @@
<div class="card-numbers" :class="size">
<span class="label typo" :class="labelFontClass">{{ label }}</span>
<div class="values" :class="size">
<span v-if="size === 'small' && max" class="value typo c2-semi-bold">{{ valueAsPercentage }}%</span>
<span v-if="size === 'small' && max" class="value typo c2-semi-bold">
{{ $n(valueAsPercentage, 'percent') }}
</span>
<div class="value typo" :class="valueFontClass">
{{ value }}<span class="unit typo" :class="unitFontClass">{{ unit }}</span>
@@ -33,10 +35,10 @@ const unitFontClass = computed(() => (props.size === 'medium' ? 'p2-medium' : 'c
const valueAsPercentage = computed(() => {
if (props.max === undefined) {
return
return 0
}
return ((props.value / props.max) * 100).toFixed(1)
return props.value / props.max
})
</script>

View File

@@ -9,8 +9,12 @@
<slot />
</div>
<div v-if="onToggleSelectAll" class="buttons">
<span v-if="selected !== 'all'" @click="emit('toggleSelectAll', true)">Select all</span>
<span v-if="selected !== 'none'" @click="emit('toggleSelectAll', false)">Deselect all</span>
<span v-if="selected !== 'all'" @click="emit('toggleSelectAll', true)">
{{ $t('core.select.all') }}
</span>
<span v-if="selected !== 'none'" @click="emit('toggleSelectAll', false)">
{{ $t('core.select.none') }}
</span>
</div>
</div>
</template>

View File

@@ -5,7 +5,9 @@
:style="{ width: percentage + '%' }"
class="stacked-bar-segment typo c4-semi-bold"
>
<div ref="ellipsisElement" :class="{ hidden }" class="ellipsis">{{ percentage.toFixed(0) }} %</div>
<div ref="ellipsisElement" :class="{ hidden }" class="ellipsis">
{{ $n(percentage / 100, 'percent') }}
</div>
</div>
</template>

View File

@@ -19,7 +19,7 @@
<MenuItem
v-for="interaction in interactions"
:key="interaction.id"
v-tooltip="$t('core.coming-soon')"
v-tooltip="$t('coming-soon')"
:disabled="interaction.disabled"
:on-click="() => updateInteraction(interaction)"
>

View File

@@ -27,6 +27,15 @@ export default createI18n({
locale: localStorage.getItem('lang') ?? 'en',
fallbackLocale: 'en',
messages,
missing: (locale, key, vm) => {
if (!import.meta.env.DEV) {
return key
}
console.warn(`i18n key not found: ${key}`, `Used in ${vm?.type.__name ?? 'unknown component'}`)
return `🌎❗⟨${key}`
},
datetimeFormats: {
en: {
date_short: {
@@ -155,4 +164,27 @@ export default createI18n({
},
},
},
numberFormats: {
en: {
percent: {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 2,
},
},
fr: {
percent: {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 2,
},
},
de: {
percent: {
style: 'percent',
minimumFractionDigits: 0,
maximumFractionDigits: 2,
},
},
},
})

View File

@@ -1,6 +1,32 @@
{
"core": {
"close": "Schließen",
"log-out": "Abmelden"
}
"alarms": "Alarme",
"bytes.ki": "KiB",
"bytes.mi": "MiB",
"bytes.gi": "GiB",
"coming-soon": "Bald verfügbar!",
"console": "Konsole",
"core.close": "Schließen",
"core.select.all": "Alles auswählen",
"core.select.none": "Alle abwählen",
"dark-mode.enable": "Aktivieren Sie den Dunkelmodus",
"dark-mode.disable": "Deaktivieren Sie den Dunkelmodus",
"dark-mode.auto": "Automatischer Dunkelmodus",
"dashboard": "Dashboard",
"documentation-name": "{name} Dokumentation",
"loading-in-progress": "Ladevorgang läuft…",
"log-out": "Abmelden",
"master": "Primärer Host",
"network": "Netzwerk",
"power-on-for-console": "Konsole ist nach Start der VM verfügbar",
"stats": "Statistiken",
"storage": "Speicher",
"support-name": "{name} pro support",
"system": "System",
"tasks": "Aufgaben"
}

View File

@@ -1,24 +1,46 @@
{
"core": {
"close": "Close",
"coming-soon": "Coming soon!",
"current": "Current",
"filter": "Filter",
"group": "Group",
"hide": "Hide",
"log-out": "Log out",
"master": "Primary host",
"open": "Open",
"quick-actions": "Quick actions",
"sidebar": {
"close": "Close sidebar",
"lock": "Lock sidebar open",
"open": "Open sidebar",
"unlock": "Unlock sidebar"
},
"sort": {
"ascending": "Sort ascending",
"descending": "Sort descending"
}
}
"alarms": "Alarms",
"bytes.ki": "KiB",
"bytes.mi": "MiB",
"bytes.gi": "GiB",
"coming-soon": "Coming soon!",
"console": "Console",
"core.close": "Close",
"core.current": "Current",
"core.filter": "Filter",
"core.group": "Group",
"core.hide": "Hide",
"core.open": "Open",
"core.quick-actions": "Quick actions",
"core.select.all": "Select all",
"core.select.none": "Select none",
"core.sidebar.close": "Close sidebar",
"core.sidebar.lock": "Lock sidebar open",
"core.sidebar.open": "Open sidebar",
"core.sidebar.unlock": "Unlock sidebar",
"core.sort.ascending": "Sort ascending",
"core.sort.descending": "Sort descending",
"dark-mode.enable": "Enable dark mode",
"dark-mode.disable": "Disable dark mode",
"dark-mode.auto": "Auto dark mode",
"dashboard": "Dashboard",
"documentation-name": "{name} documentation",
"loading-in-progress": "Loading in progress…",
"log-out": "Log out",
"master": "Primary host",
"network": "Network",
"power-on-for-console": "Power on your VM to access its console",
"stats": "Stats",
"storage": "Storage",
"support-name": "{name} pro support",
"system": "System",
"tasks": "Tasks"
}

View File

@@ -1,24 +1,46 @@
{
"core": {
"close": "Fermer",
"coming-soon": "Bientôt disponible !",
"current": "Actuel",
"filter": "Filtrer",
"group": "Grouper",
"hide": "Masquer",
"log-out": "Se déconnecter",
"master": "Hôte primaire",
"open": "Ouvrir",
"quick-actions": "Actions rapides",
"sidebar": {
"close": "Fermer la barre latérale",
"lock": "Verrouiller la barre latérale",
"open": "Ouvrir la barre latérale",
"unlock": "Déverrouiller la barre latérale"
},
"sort": {
"ascending": "Trier par ordre croissant",
"descending": "Trier par ordre décroissant"
}
}
"alarms": "Alarmes",
"bytes.ki": "Kio",
"bytes.mi": "Mio",
"bytes.gi": "Gio",
"coming-soon": "Bientôt disponible !",
"console": "Console",
"core.close": "Fermer",
"core.current": "Actuel",
"core.filter": "Filtrer",
"core.group": "Grouper",
"core.hide": "Masquer",
"core.open": "Ouvrir",
"core.quick-actions": "Actions rapides",
"core.select.all": "Tout sélectionner",
"core.select.none": "Tout désélectionner",
"core.sidebar.close": "Fermer la barre latérale",
"core.sidebar.lock": "Verrouiller la barre latérale",
"core.sidebar.open": "Ouvrir la barre latérale",
"core.sidebar.unlock": "Déverrouiller la barre latérale",
"core.sort.ascending": "Trier par ordre croissant",
"core.sort.descending": "Trier par ordre décroissant",
"dark-mode.enable": "Activer le mode sombre",
"dark-mode.disable": "Désactiver le mode sombre",
"dark-mode.auto": "Mode sombre automatique",
"dashboard": "Tableau de bord",
"documentation-name": "Documentation {name}",
"loading-in-progress": "Chargement en cours…",
"log-out": "Se déconnecter",
"master": "Hôte primaire",
"network": "Réseau",
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
"stats": "Stats",
"storage": "Stockage",
"support-name": "Support pro {name}",
"system": "Système",
"tasks": "Tâches"
}

View File

@@ -24,7 +24,7 @@
</a>
</MenuItem>
<MenuItem :icon="faArrowRightFromBracket" class="logout" @click="logout()">
{{ $t('core.log-out') }}
{{ $t('log-out') }}
</MenuItem>
</MenuList>
</template>

View File

@@ -3,7 +3,7 @@
<TreeItemLabel :icon="faServer" :no-indent="!hasVMs" :route="`/host/${host.id}`">
{{ host.name_label }}
<template #addons>
<UiIcon v-if="isMaster" v-tooltip="$t('core.master')" :icon="faStar" color="warning" />
<UiIcon v-if="isMaster" v-tooltip="$t('master')" :icon="faStar" color="warning" />
</template>
</TreeItemLabel>
<template v-if="hasVMs" #sublist>

View File

@@ -4,14 +4,14 @@
<h4 v-tooltip class="typo h4-medium title">{{ name }}</h4>
</div>
<TabList>
<TabItem disabled>Dashboard</TabItem>
<TabItem active>Console</TabItem>
<TabItem disabled>Alarms</TabItem>
<TabItem disabled>Stats</TabItem>
<TabItem disabled>System</TabItem>
<TabItem disabled>Network</TabItem>
<TabItem disabled>Storage</TabItem>
<TabItem disabled>Tasks</TabItem>
<TabItem disabled>{{ $t('dashboard') }}</TabItem>
<TabItem active>{{ $t('console') }}</TabItem>
<TabItem disabled>{{ $t('alarms') }}</TabItem>
<TabItem disabled>{{ $t('stats') }}</TabItem>
<TabItem disabled>{{ $t('system') }}</TabItem>
<TabItem disabled>{{ $t('network') }}</TabItem>
<TabItem disabled>{{ $t('storage') }}</TabItem>
<TabItem disabled>{{ $t('tasks') }}</TabItem>
</TabList>
</template>

View File

@@ -0,0 +1,4 @@
{
"account-organization-more": "Konto, Organisation & mehr…",
"tasks.quick-view": "Schnellansicht der Aufgaben (Bald verfügbar)"
}

View File

@@ -1,9 +1,4 @@
{
"account-organization-more": "Account, organization & more...",
"documentation-name": "{name} documentation",
"power-on-for-console": "Power on your VM to access its console",
"support-name": "{name} pro support",
"tasks": {
"quick-view": "Tasks' quick view (coming soon)"
}
"account-organization-more": "Account, organization & more",
"tasks.quick-view": "Tasks' quick view (coming soon)"
}

View File

@@ -1,9 +1,4 @@
{
"account-organization-more": "Compte, organisation et plus…",
"documentation-name": "Documentation {name}",
"power-on-for-console": "Allumez votre VM pour accéder à sa console",
"support-name": "Support pro {name}",
"tasks": {
"quick-view": "Vue rapide des tâches (bientôt disponible)"
}
"tasks.quick-view": "Vue rapide des tâches (bientôt disponible)"
}

View File

@@ -1,6 +1,6 @@
<template>
<DefaultLayout>
<template #content> Coming soon</template>
<template #content>{{ $t('coming-soon') }}</template>
</DefaultLayout>
</template>

View File

@@ -1,6 +1,10 @@
<template>
<DefaultLayout>
<template #content>Welcome to XO 6</template>
<template #content>
<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
Welcome to XO 6
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
</template>
</DefaultLayout>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<DefaultLayout>
<template #content> Coming soon</template>
<template #content>{{ $t('coming-soon') }}</template>
</DefaultLayout>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<DefaultLayout>
<template #content> Coming soon</template>
<template #content>{{ $t('coming-soon') }}</template>
</DefaultLayout>
</template>

View File

@@ -7,6 +7,7 @@
"@babel/register": "^7.0.0",
"@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^18.1.0",
"@intlify/eslint-plugin-vue-i18n": "^3.0.0",
"@limegrass/eslint-plugin-import-alias": "^1.1.0",
"@typescript-eslint/parser": "^7.12.0",
"@typescript-eslint/eslint-plugin": "^7.12.0",

View File

@@ -1787,6 +1787,13 @@
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.14.0":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.24.0", "@babel/template@^7.3.3":
version "7.24.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50"
@@ -2168,6 +2175,21 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/eslintrc@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6"
integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^10.0.1"
globals "^14.0.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.57.0":
version "8.57.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
@@ -2268,7 +2290,7 @@
source-map-js "^1.0.1"
yaml-eslint-parser "^1.2.2"
"@intlify/core-base@9.13.1":
"@intlify/core-base@9.13.1", "@intlify/core-base@^9.12.0":
version "9.13.1"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.13.1.tgz#bd1f38e665095993ef9b67aeeb794f3cabcb515d"
integrity sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==
@@ -2276,7 +2298,32 @@
"@intlify/message-compiler" "9.13.1"
"@intlify/shared" "9.13.1"
"@intlify/message-compiler@9.13.1", "@intlify/message-compiler@^9.4.0":
"@intlify/eslint-plugin-vue-i18n@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@intlify/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-3.0.0.tgz#3714d15038b805e3755ef2180e0129ce09cb64ad"
integrity sha512-s4fe+VOiqMZGhDrXWnL1xLyHbcFWBcEBeD/KpVrkOtL+utH2LPTi7uZ8RvWSthMS0mUL/7L74hFJ//OUU7AYww==
dependencies:
"@eslint/eslintrc" "^3.0.0"
"@intlify/core-base" "^9.12.0"
"@intlify/message-compiler" "^9.12.0"
debug "^4.3.4"
eslint-compat-utils "^0.5.0"
glob "^10.3.3"
globals "^15.0.0"
ignore "^5.2.4"
import-fresh "^3.3.0"
is-language-code "^3.1.0"
js-yaml "^4.1.0"
json5 "^2.2.3"
jsonc-eslint-parser "^2.3.0"
lodash "^4.17.21"
parse5 "^7.1.2"
semver "^7.5.4"
synckit "^0.9.0"
vue-eslint-parser "^9.3.1"
yaml-eslint-parser "^1.2.2"
"@intlify/message-compiler@9.13.1", "@intlify/message-compiler@^9.12.0", "@intlify/message-compiler@^9.4.0":
version "9.13.1"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.13.1.tgz#ff8129badf77db3fb648b8d3cceee87c8033ed0a"
integrity sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==
@@ -10469,6 +10516,11 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
eslint@^8.7.0:
version "8.57.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
@@ -10528,6 +10580,15 @@ esniff@^2.0.1:
event-emitter "^0.3.5"
type "^2.7.2"
espree@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==
dependencies:
acorn "^8.11.3"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.0.0"
espree@^9.0.0, espree@^9.3.1, espree@^9.6.0, espree@^9.6.1:
version "9.6.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
@@ -11920,6 +11981,16 @@ globals@^13.19.0, globals@^13.24.0:
dependencies:
type-fest "^0.20.2"
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globals@^15.0.0:
version "15.4.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-15.4.0.tgz#3e36ea6e4d9ddcf1cb42d92f5c4a145a8a2ddc1c"
integrity sha512-unnwvMZpv0eDUyjNyh9DH/yxUaRYrEjW/qK4QcdrHg3oO11igUQrCSgODHEqxlKg8v2CD2Sd7UkqqEBoz5U7TQ==
globalthis@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
@@ -13557,6 +13628,13 @@ is-lambda@^1.0.1:
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==
is-language-code@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-language-code/-/is-language-code-3.1.0.tgz#b2386b49227e7010636f16d0c2c681ca40136ab5"
integrity sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==
dependencies:
"@babel/runtime" "^7.14.0"
is-lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a"
@@ -17327,7 +17405,7 @@ parse5-htmlparser2-tree-adapter@^7.0.0:
domhandler "^5.0.2"
parse5 "^7.0.0"
parse5@^7.0.0:
parse5@^7.0.0, parse5@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
@@ -21321,6 +21399,14 @@ synckit@^0.8.6:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
synckit@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.0.tgz#5b33b458b3775e4466a5b377fba69c63572ae449"
integrity sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==
dependencies:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
syntax-error@^1.1.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c"