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

+59 -29
View File
@@ -1,7 +1,9 @@
<script setup lang="ts">
import { t, formatNumber } from '@/i18n'
import { computed, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useToast } from '@nuxt/ui/composables'
import { useLocalizedToast } from '@/i18n/presentation'
import DashboardShell from '@/components/DashboardShell.vue'
import ArticleStatusBadge from '@/components/ArticleStatusBadge.vue'
import NewArticleModal from '@/components/NewArticleModal.vue'
@@ -19,7 +21,7 @@ const RECENT_LIST_SIZE = 5
type LoadState = 'loading' | 'default' | 'empty' | 'error'
const router = useRouter()
const toast = useToast()
const toast = useLocalizedToast()
const session = useSessionStore()
const update = useUpdateStore()
const { listArticles } = useArticles()
@@ -32,12 +34,12 @@ let skeletonTimer: ReturnType<typeof setTimeout> | null = null
function greetingForHour(hour: number) {
if (hour < 12) {
return 'Good morning'
return t('Good morning')
}
if (hour < 18) {
return 'Good afternoon'
return t('Good afternoon')
}
return 'Good evening'
return t('Good evening')
}
const greeting = computed(() => {
@@ -48,8 +50,8 @@ const greeting = computed(() => {
const subtitle = computed(() =>
loadState.value === 'empty'
? 'Welcome to RenewCMS.'
: "Here's where things stand on your site.",
? t('Welcome to RenewCMS.')
: t("Here's where things stand on your site."),
)
const stats = computed(() => {
@@ -62,9 +64,24 @@ const stats = computed(() => {
})
const statTiles = computed(() => [
{ label: 'articles', value: stats.value.total },
{ label: 'online', value: stats.value.online },
{ label: 'drafts', value: stats.value.drafts },
{
get label() {
return t('articles')
},
value: stats.value.total,
},
{
get label() {
return t('online')
},
value: stats.value.online,
},
{
get label() {
return t('drafts')
},
value: stats.value.drafts,
},
])
const recentArticles = computed(() =>
@@ -106,13 +123,17 @@ function openNewArticleModal() {
function onArticleCreated(article: Article) {
router.push(`/article/${article.id}/edit`)
toast.add({
toast.add(() => ({
id: `article-created-${article.id}`,
color: 'success',
icon: 'i-lucide-circle-check',
title: 'Draft created',
description: `"${article.title}" is saved as a draft.`,
})
get title() {
return t('Draft created')
},
get description() {
return t('\u0022{0}\u0022 is saved as a draft.', [article.title])
},
}))
}
const readyBannerDismissed = ref(false)
@@ -154,7 +175,7 @@ onMounted(loadArticles)
variant="ghost"
class="size-11 justify-center text-highlighted sm:hidden"
:ui="{ leadingIcon: 'size-5' }"
aria-label="New article"
:aria-label="t('New article')"
@click="openNewArticleModal"
/>
</template>
@@ -172,7 +193,7 @@ onMounted(loadArticles)
class="hidden px-3 sm:flex"
@click="openNewArticleModal"
>
New article
{{ t('New article') }}
</UButton>
</div>
@@ -198,7 +219,7 @@ onMounted(loadArticles)
</template>
<template v-else>
<span class="text-2xl font-semibold text-highlighted">{{
tile.value
formatNumber(tile.value)
}}</span>
<span class="text-muted">{{ tile.label }}</span>
</template>
@@ -207,9 +228,11 @@ onMounted(loadArticles)
<section class="flex flex-col gap-2.5">
<div class="flex items-baseline justify-between">
<h2 class="text-lg font-semibold text-highlighted">Recently updated</h2>
<h2 class="text-lg font-semibold text-highlighted">
{{ t('Recently updated') }}
</h2>
<RouterLink to="/article" class="text-toned underline underline-offset-3">
All articles
{{ t('All articles') }}
</RouterLink>
</div>
<div
@@ -249,8 +272,10 @@ onMounted(loadArticles)
v-else-if="loadState === 'empty'"
variant="outline"
icon="i-lucide-file-text"
title="Write your first article"
description="It starts as a draft, so nothing appears on your site until you publish it."
:title="t('Write your first article')"
:description="
t('It starts as a draft, so nothing appears on your site until you publish it.')
"
:ui="{
root: 'gap-3 rounded-none border border-default px-6 py-12 ring-0 sm:px-6 sm:py-12 lg:px-6 lg:py-12',
header: 'max-w-none gap-3',
@@ -266,7 +291,7 @@ onMounted(loadArticles)
class="h-11 px-4 lg:h-8"
@click="openNewArticleModal"
>
New article
{{ t('New article') }}
</UButton>
</template>
</UEmpty>
@@ -279,8 +304,8 @@ onMounted(loadArticles)
orientation="vertical"
:close="false"
icon="i-lucide-circle-x"
title="Couldn't load your articles"
description="Check your connection and try again."
:title="t('Couldn\'t load your articles')"
:description="t('Check your connection and try again.')"
:ui="{ actions: 'mt-2' }"
>
<template #actions>
@@ -292,7 +317,7 @@ onMounted(loadArticles)
:ui="{ leadingIcon: 'size-3.5' }"
@click="loadArticles"
>
Try again
{{ t('Try again') }}
</UButton>
</template>
</UAlert>
@@ -304,16 +329,21 @@ onMounted(loadArticles)
<UIcon name="i-lucide-plug" class="size-4.5" />
</div>
<div class="flex flex-1 flex-col gap-1">
<h2 class="text-base/5 font-semibold text-highlighted">Connect your website</h2>
<h2 class="text-base/5 font-semibold text-highlighted">
{{ t('Connect your website') }}
</h2>
<p class="text-pretty text-muted">
Your site reads published articles from RenewCMS. Share this guide with
whoever builds it.
{{
t(
'Your site reads published articles from RenewCMS. Share this guide with whoever builds it.',
)
}}
</p>
<RouterLink
to="/integration"
class="mt-1 self-start font-medium text-highlighted underline underline-offset-3"
>
Open the guide
{{ t('Open the guide') }}
</RouterLink>
</div>
</div>