feat(SPA): i18n internationzalization, english and french

This commit is contained in:
fsylvain committed 2026-10-03 01:53:49 +02:00
1 parent 7f1e8e572d
commit 721311a643
88 files changed
+4438 -1148

No files matched your search

+161 -51
View File
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { computed, nextTick, ref, watch } from 'vue'
import ReleaseNotes from '@/components/ReleaseNotes.vue'
import ServerCommand from '@/components/ServerCommand.vue'
@@ -61,8 +63,12 @@ const READY_HINT =
const LIMITED_BLOCK: Block = {
tone: 'info',
icon: 'i-lucide-info',
title: 'Too many tries',
text: 'Wait a moment before trying again.',
get title() {
return t('Too many tries')
},
get text() {
return t('Wait a moment before trying again.')
},
}
const ZERO_PROGRESS = { percent: 0, downloaded_bytes: 0, total_bytes: 0 }
@@ -147,47 +153,90 @@ const applyBlocks = computed<Record<ApplyView, Block>>(() => ({
available: {
tone: 'info',
icon: 'i-lucide-circle-arrow-up',
title: `Version ${newVersion.value} is available`,
text: `You have ${props.status.current}. Update downloads the new version and checks it. Nothing changes until you restart.`,
get title() {
return t('Version {0} is available', [newVersion.value])
},
get text() {
return t(
'You have {0}. Update downloads the new version and checks it. Nothing changes until you restart.',
[props.status.current],
)
},
meta: lastChecked.value,
},
downloading: {
tone: 'info',
icon: 'i-lucide-download',
title: `Downloading version ${newVersion.value}…`,
text: 'You can leave this page. The download continues and your site stays online.',
get title() {
return t('Downloading version {0}…', [newVersion.value])
},
get text() {
return t('You can leave this page. The download continues and your site stays online.')
},
},
verifying: {
tone: 'info',
icon: 'i-lucide-shield-check',
title: 'Checking the download…',
text: 'Making sure the file is complete and safe to install. This takes a few seconds.',
get title() {
return t('Checking the download…')
},
get text() {
return t(
'Making sure the file is complete and safe to install. This takes a few seconds.',
)
},
},
ready: {
tone: 'success',
icon: 'i-lucide-circle-check',
title: `Version ${newVersion.value} is ready to install`,
text: 'The download is checked. Restart to install it. Your site will be unavailable for about 10 seconds.',
meta: 'Nothing has changed yet.',
get title() {
return t('Version {0} is ready to install', [newVersion.value])
},
get text() {
return t(
'The download is checked. Restart to install it. Your site will be unavailable for about 10 seconds.',
)
},
meta: t('Nothing has changed yet.'),
},
'download-failed': {
tone: 'error',
icon: 'i-lucide-circle-x',
title: "The download didn't finish",
text: 'The connection dropped before the file was complete. Nothing was changed and your site is running as before.',
get title() {
return t("The download didn't finish")
},
get text() {
return t(
'The connection dropped before the file was complete. Nothing was changed and your site is running as before.',
)
},
},
'verification-failed': {
tone: 'warning',
icon: 'i-lucide-triangle-alert',
title: "We didn't use this update",
text: "The file didn't pass our safety check, so it was not installed. Nothing was changed. Try downloading it again. If this keeps happening, contact RenewCMS support.",
get title() {
return t("We didn't use this update")
},
get text() {
return t(
"The file didn't pass our safety check, so it was not installed. Nothing was changed. Try downloading it again. If this keeps happening, contact RenewCMS support.",
)
},
},
'rolled-back': {
tone: 'warning',
icon: 'i-lucide-triangle-alert',
title: "The update didn't install",
text: 'RenewCMS put the previous version back, so your site is running as before. Nothing was lost.',
meta: 'What to do next: try again in a few minutes. If it fails again, ask whoever manages your server to check the update.',
get title() {
return t("The update didn't install")
},
get text() {
return t(
'RenewCMS put the previous version back, so your site is running as before. Nothing was lost.',
)
},
meta: t(
'What to do next: try again in a few minutes. If it fails again, ask whoever manages your server to check the update.',
),
},
limited: LIMITED_BLOCK,
}))
@@ -196,13 +245,23 @@ const progressView = computed<ProgressView | null>(() => {
if (applyView.value === 'downloading') {
const progress = props.status.progress ?? ZERO_PROGRESS
return {
label: `Downloading version ${newVersion.value}`,
get label() {
return t('Downloading version {0}', [newVersion.value])
},
text: props.status.progress ? formatDownloadProgress(progress) : '0%',
value: progress.percent,
}
}
if (applyView.value === 'verifying') {
return { label: VERIFYING_ANNOUNCEMENT, text: 'Almost there', value: null }
return {
get label() {
return t(VERIFYING_ANNOUNCEMENT)
},
get text() {
return t('Almost there')
},
value: null,
}
}
return null
})
@@ -211,23 +270,25 @@ const announcement = computed(() => {
if (applyView.value === 'downloading') {
return downloadAnnouncement(newVersion.value, props.status.progress?.percent ?? 0)
}
return applyView.value === 'verifying' ? VERIFYING_ANNOUNCEMENT : ''
return applyView.value === 'verifying' ? t(VERIFYING_ANNOUNCEMENT) : ''
})
const hint = computed(() => {
if (applyView.value === 'available') {
return AVAILABLE_HINT
return t(AVAILABLE_HINT)
}
if (applyView.value === 'ready') {
return READY_HINT
return t(READY_HINT)
}
return showRestartHint.value ? RESTART_HINT : null
return showRestartHint.value ? t(RESTART_HINT) : null
})
function applyButton(view: ApplyView): ActionButton {
if (isStarting.value || view === 'downloading' || view === 'verifying') {
return {
label: 'Updating…',
get label() {
return t('Updating…')
},
icon: 'i-lucide-loader-circle',
primary: false,
disabled: true,
@@ -236,7 +297,9 @@ function applyButton(view: ApplyView): ActionButton {
}
if (view === 'ready') {
return {
label: 'Restart',
get label() {
return t('Restart')
},
icon: 'i-lucide-power',
primary: true,
disabled: false,
@@ -249,7 +312,9 @@ function applyButton(view: ApplyView): ActionButton {
}
if (view === 'available') {
return {
label: 'Update',
get label() {
return t('Update')
},
icon: 'i-lucide-download',
primary: true,
disabled: false,
@@ -257,7 +322,9 @@ function applyButton(view: ApplyView): ActionButton {
}
}
return {
label: 'Try again',
get label() {
return t('Try again')
},
icon: 'i-lucide-rotate-cw',
primary: true,
disabled: false,
@@ -270,8 +337,12 @@ const block = computed<Block>(() => {
return {
tone: 'neutral',
icon: 'i-lucide-loader-circle',
title: 'Checking for updates…',
text: 'This usually takes a few seconds.',
get title() {
return t('Checking for updates…')
},
get text() {
return t('This usually takes a few seconds.')
},
}
}
if (isOwner.value && props.phase === 'limited') {
@@ -281,8 +352,14 @@ const block = computed<Block>(() => {
return {
tone: 'warning',
icon: 'i-lucide-wifi-off',
title: "Couldn't check for updates",
text: "RenewCMS couldn't reach the update server. Check your connection and try again. Your site isn't affected.",
get title() {
return t("Couldn't check for updates")
},
get text() {
return t(
"RenewCMS couldn't reach the update server. Check your connection and try again. Your site isn't affected.",
)
},
meta: lastChecked.value,
}
}
@@ -293,8 +370,12 @@ const block = computed<Block>(() => {
return {
tone: 'success',
icon: 'i-lucide-circle-check',
title: `You're on version ${displayVersion(props.status.current)}`,
text: 'The update is installed and your site is running again.',
get title() {
return t("You're on version {0}", [displayVersion(props.status.current)])
},
get text() {
return t('The update is installed and your site is running again.')
},
meta: props.status.finished_at ? formatUpdatedAt(props.status.finished_at) : undefined,
}
}
@@ -302,25 +383,42 @@ const block = computed<Block>(() => {
return {
tone: 'info',
icon: 'i-lucide-info',
title: `Version ${newVersion.value} is available`,
text: "This site can't update itself from the browser yet. Whoever manages your server can set it up once, or update RenewCMS for you.",
get title() {
return t('Version {0} is available', [newVersion.value])
},
get text() {
return t(
"This site can't update itself from the browser yet. Whoever manages your server can set it up once, or update RenewCMS for you.",
)
},
}
}
if (props.status.available) {
return {
tone: 'info',
icon: 'i-lucide-circle-arrow-up',
title: `Version ${newVersion.value} is available`,
text: `You have ${props.status.current}. Only the owner can update RenewCMS. Ask the owner of this site.`,
get title() {
return t('Version {0} is available', [newVersion.value])
},
get text() {
return t(
'You have {0}. Only the owner can update RenewCMS. Ask the owner of this site.',
[props.status.current],
)
},
}
}
return {
tone: 'success',
icon: 'i-lucide-circle-check',
title: "You're up to date",
text: isOwner.value
? 'You have the latest version of RenewCMS.'
: 'Only the owner can update or restart the site.',
get title() {
return t("You're up to date")
},
get text() {
return isOwner.value
? t('You have the latest version of RenewCMS.')
: t('Only the owner can update or restart the site.')
},
meta: isOwner.value ? lastChecked.value : undefined,
}
})
@@ -342,7 +440,9 @@ const showServerGuidance = computed(
)
const plainRestartButton: ActionButton = {
label: 'Restart',
get label() {
return t('Restart')
},
icon: 'i-lucide-power',
primary: false,
disabled: false,
@@ -357,7 +457,9 @@ const actionButtons = computed<ActionButton[]>(() => {
if (props.phase === 'checking') {
return [
{
label: 'Checking…',
get label() {
return t('Checking…')
},
icon: 'i-lucide-loader-circle',
primary: false,
disabled: true,
@@ -367,7 +469,9 @@ const actionButtons = computed<ActionButton[]>(() => {
}
if (props.phase === 'failed') {
return withPlainRestart({
label: 'Try again',
get label() {
return t('Try again')
},
icon: 'i-lucide-rotate-cw',
primary: true,
disabled: false,
@@ -384,7 +488,9 @@ const actionButtons = computed<ActionButton[]>(() => {
return []
}
return withPlainRestart({
label: 'Check for updates',
get label() {
return t('Check for updates')
},
icon: 'i-lucide-refresh-cw',
primary: false,
disabled: false,
@@ -401,14 +507,18 @@ function withPlainRestart(button: ActionButton) {
function limitedButton(action: ButtonAction): ActionButton {
return props.retryActive
? {
label: `Try again in ${props.retryLabel}`,
get label() {
return t('Try again in {0}', [props.retryLabel])
},
icon: 'i-lucide-clock',
primary: false,
disabled: true,
action: null,
}
: {
label: 'Try again',
get label() {
return t('Try again')
},
icon: 'i-lucide-rotate-cw',
primary: false,
disabled: false,
@@ -458,7 +568,7 @@ const notes = computed(() => {
tabindex="-1"
class="self-start text-lg font-semibold text-highlighted focus:outline-2 focus:outline-offset-4 focus:outline-primary"
>
Updates
{{ t('Updates') }}
</h2>
<UpdateStatusBlock
@@ -516,7 +626,7 @@ const notes = computed(() => {
rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 self-start text-sm font-medium text-highlighted underline underline-offset-3"
>
How to update
{{ t('How to update') }}
<UIcon name="i-lucide-arrow-up-right" class="size-3.5" />
</a>
@@ -527,7 +637,7 @@ const notes = computed(() => {
rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 self-start text-sm font-medium text-highlighted underline underline-offset-3"
>
Read the release notes
{{ t('Read the release notes') }}
<UIcon name="i-lucide-arrow-up-right" class="size-3.5" />
</a>
</section>