chore: more i18n, will it ever end?

This commit is contained in:
2026-02-21 12:58:34 +01:00
parent 16debdd332
commit dbef7ef98b
7 changed files with 151 additions and 23 deletions

View File

@@ -0,0 +1,9 @@
## JSX text nodes (tsx)
## UI props with literal text
## Dialogs/prompts/alerts/confirms
## Toasts and notifications with literals
## Main-process menu/dialog labels

View File

@@ -1453,8 +1453,6 @@ const formatBytes = (bytes: number): string => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}; };
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
interface DashboardStats { interface DashboardStats {
totalPosts: number; totalPosts: number;
draftCount: number; draftCount: number;
@@ -1479,6 +1477,7 @@ interface TagDataWithColor {
} }
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const { t: tr, language } = useI18n();
const { posts, media } = useAppStore(); const { posts, media } = useAppStore();
const [stats, setStats] = useState<DashboardStats | null>(null); const [stats, setStats] = useState<DashboardStats | null>(null);
const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]); const [yearMonthData, setYearMonthData] = useState<{ year: number; month: number; count: number }[]>([]);
@@ -1486,6 +1485,12 @@ const Dashboard: React.FC = () => {
const [tagColors, setTagColors] = useState<Map<string, string>>(new Map()); const [tagColors, setTagColors] = useState<Map<string, string>>(new Map());
const [categoryCounts, setCategoryCounts] = useState<CategoryCount[]>([]); const [categoryCounts, setCategoryCounts] = useState<CategoryCount[]>([]);
const uiDateLocale = UI_DATE_LOCALE[language] || UI_DATE_LOCALE.en;
const monthFormatter = useMemo(
() => new Intl.DateTimeFormat(uiDateLocale, { month: 'short' }),
[uiDateLocale]
);
useEffect(() => { useEffect(() => {
const loadStats = async () => { const loadStats = async () => {
try { try {
@@ -1553,49 +1558,63 @@ const Dashboard: React.FC = () => {
const displayPublishedCount = stats?.publishedCount ?? 0; const displayPublishedCount = stats?.publishedCount ?? 0;
const displayArchivedCount = stats?.archivedCount ?? 0; const displayArchivedCount = stats?.archivedCount ?? 0;
const getPostCountLabel = useCallback((count: number) => {
return tr(count === 1 ? 'dashboard.postCount.one' : 'dashboard.postCount.other', { count });
}, [tr]);
const getPostStatusLabel = useCallback((status: string) => {
const statusKeyByValue: Record<string, string> = {
draft: 'dashboard.status.draft',
published: 'dashboard.status.published',
archived: 'dashboard.status.archived',
};
const key = statusKeyByValue[status];
return key ? tr(key) : status;
}, [tr]);
return ( return (
<div className="editor-empty"> <div className="editor-empty">
<div className="dashboard-content"> <div className="dashboard-content">
<h1>Dashboard</h1> <h1>{tr('dashboard.title')}</h1>
<p className="text-muted">Overview of your blog database</p> <p className="text-muted">{tr('dashboard.subtitle')}</p>
<div className="dashboard-stats"> <div className="dashboard-stats">
<div className="stat-card"> <div className="stat-card">
<div className="stat-number">{displayTotalPosts}</div> <div className="stat-number">{displayTotalPosts}</div>
<div className="stat-label">Total Posts</div> <div className="stat-label">{tr('dashboard.stats.totalPosts')}</div>
<div className="stat-breakdown"> <div className="stat-breakdown">
<span className="stat-tag stat-published">{displayPublishedCount} published</span> <span className="stat-tag stat-published">{tr('dashboard.stats.published', { count: displayPublishedCount })}</span>
<span className="stat-tag stat-draft">{displayDraftCount} drafts</span> <span className="stat-tag stat-draft">{tr('dashboard.stats.drafts', { count: displayDraftCount })}</span>
{displayArchivedCount > 0 && <span className="stat-tag stat-archived">{displayArchivedCount} archived</span>} {displayArchivedCount > 0 && <span className="stat-tag stat-archived">{tr('dashboard.stats.archived', { count: displayArchivedCount })}</span>}
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-number">{media.length}</div> <div className="stat-number">{media.length}</div>
<div className="stat-label">Media Files</div> <div className="stat-label">{tr('dashboard.stats.mediaFiles')}</div>
<div className="stat-breakdown"> <div className="stat-breakdown">
<span className="stat-tag">{imageCount} images</span> <span className="stat-tag">{tr('dashboard.stats.images', { count: imageCount })}</span>
<span className="stat-tag">{formatBytes(totalMediaSize)}</span> <span className="stat-tag">{formatBytes(totalMediaSize)}</span>
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-number">{tagCounts.length}</div> <div className="stat-number">{tagCounts.length}</div>
<div className="stat-label">Tags</div> <div className="stat-label">{tr('dashboard.stats.tags')}</div>
<div className="stat-breakdown"> <div className="stat-breakdown">
<span className="stat-tag">{categoryCounts.length} categories</span> <span className="stat-tag">{tr('dashboard.stats.categories', { count: categoryCounts.length })}</span>
</div> </div>
</div> </div>
</div> </div>
{timelineEntries.length > 0 && ( {timelineEntries.length > 0 && (
<div className="dashboard-section"> <div className="dashboard-section">
<h4>Posts Over Time</h4> <h4>{tr('dashboard.section.postsOverTime')}</h4>
<div className="timeline-chart"> <div className="timeline-chart">
{timelineEntries.map((entry) => ( {timelineEntries.map((entry) => (
<div key={`${entry.year}-${entry.month}`} className="timeline-bar-container"> <div key={`${entry.year}-${entry.month}`} className="timeline-bar-container">
<div className="timeline-bar" style={{ height: `${(entry.count / maxCount) * 100}%` }}> <div className="timeline-bar" style={{ height: `${(entry.count / maxCount) * 100}%` }}>
<span className="timeline-bar-count">{entry.count}</span> <span className="timeline-bar-count">{entry.count}</span>
</div> </div>
<div className="timeline-bar-label">{MONTH_NAMES[entry.month]}</div> <div className="timeline-bar-label">{monthFormatter.format(new Date(entry.year, entry.month, 1))}</div>
</div> </div>
))} ))}
</div> </div>
@@ -1604,7 +1623,7 @@ const Dashboard: React.FC = () => {
{tagCloudItems.length > 0 && ( {tagCloudItems.length > 0 && (
<div className="dashboard-section"> <div className="dashboard-section">
<h4>Tags</h4> <h4>{tr('dashboard.section.tags')}</h4>
<div className="tag-cloud"> <div className="tag-cloud">
{tagCloudItems.map(item => { {tagCloudItems.map(item => {
const hasColor = !!item.color; const hasColor = !!item.color;
@@ -1620,26 +1639,26 @@ const Dashboard: React.FC = () => {
key={item.tag} key={item.tag}
className={`dashboard-tag ${hasColor ? 'has-color' : ''}`} className={`dashboard-tag ${hasColor ? 'has-color' : ''}`}
style={style} style={style}
title={`${item.count} post${item.count !== 1 ? 's' : ''}`} title={getPostCountLabel(item.count)}
> >
{item.tag} {item.tag}
</span> </span>
); );
})} })}
{tagCounts.length > 40 && <span className="text-muted tag-cloud-more">+{tagCounts.length - 40} more</span>} {tagCounts.length > 40 && <span className="text-muted tag-cloud-more">{tr('dashboard.tagCloud.more', { count: tagCounts.length - 40 })}</span>}
</div> </div>
</div> </div>
)} )}
{categoryCounts.length > 0 && ( {categoryCounts.length > 0 && (
<div className="dashboard-section"> <div className="dashboard-section">
<h4>Categories</h4> <h4>{tr('dashboard.section.categories')}</h4>
<div className="tag-cloud"> <div className="tag-cloud">
{categoryCounts.map(cat => ( {categoryCounts.map(cat => (
<span <span
key={cat.category} key={cat.category}
className="dashboard-tag dashboard-category" className="dashboard-tag dashboard-category"
title={`${cat.count} post${cat.count !== 1 ? 's' : ''}`} title={getPostCountLabel(cat.count)}
> >
{cat.category} <span className="tag-count">{cat.count}</span> {cat.category} <span className="tag-count">{cat.count}</span>
</span> </span>
@@ -1650,7 +1669,7 @@ const Dashboard: React.FC = () => {
{recentPosts.length > 0 && ( {recentPosts.length > 0 && (
<div className="dashboard-section"> <div className="dashboard-section">
<h4>Recently Updated</h4> <h4>{tr('dashboard.section.recentlyUpdated')}</h4>
<div className="recent-posts-list"> <div className="recent-posts-list">
{recentPosts.map(post => ( {recentPosts.map(post => (
<div <div
@@ -1667,9 +1686,9 @@ const Dashboard: React.FC = () => {
useAppStore.getState().openTab({ type: 'post', id: post.id, isTransient: false }); useAppStore.getState().openTab({ type: 'post', id: post.id, isTransient: false });
}} }}
> >
<span className="recent-post-title">{post.title || 'Untitled'}</span> <span className="recent-post-title">{post.title || tr('editor.untitled')}</span>
<span className={`recent-post-status status-${post.status}`}>{post.status}</span> <span className={`recent-post-status status-${post.status}`}>{getPostStatusLabel(post.status)}</span>
<span className="recent-post-date">{new Date(post.updatedAt).toLocaleDateString()}</span> <span className="recent-post-date">{new Date(post.updatedAt).toLocaleDateString(uiDateLocale)}</span>
</div> </div>
))} ))}
</div> </div>

View File

@@ -411,6 +411,26 @@
"editor.footer.created": "Erstellt", "editor.footer.created": "Erstellt",
"editor.footer.updated": "Aktualisiert", "editor.footer.updated": "Aktualisiert",
"editor.footer.published": "Veröffentlicht", "editor.footer.published": "Veröffentlicht",
"dashboard.title": "Übersicht",
"dashboard.subtitle": "Überblick über deine Blog-Datenbank",
"dashboard.stats.totalPosts": "Beiträge gesamt",
"dashboard.stats.published": "{count} veröffentlicht",
"dashboard.stats.drafts": "{count} Entwürfe",
"dashboard.stats.archived": "{count} archiviert",
"dashboard.stats.mediaFiles": "Mediendateien",
"dashboard.stats.images": "{count} Bilder",
"dashboard.stats.tags": "Schlagwörter",
"dashboard.stats.categories": "{count} Kategorien",
"dashboard.section.postsOverTime": "Beiträge im Zeitverlauf",
"dashboard.section.tags": "Schlagwörter",
"dashboard.section.categories": "Kategorien",
"dashboard.section.recentlyUpdated": "Kürzlich aktualisiert",
"dashboard.tagCloud.more": "+{count} weitere",
"dashboard.postCount.one": "{count} Beitrag",
"dashboard.postCount.other": "{count} Beiträge",
"dashboard.status.draft": "Entwurf",
"dashboard.status.published": "Veröffentlicht",
"dashboard.status.archived": "Archiviert",
"projectSelector.switchProject": "Projekt wechseln", "projectSelector.switchProject": "Projekt wechseln",
"projectSelector.selectProject": "Projekt auswählen", "projectSelector.selectProject": "Projekt auswählen",
"projectSelector.projectsHeader": "Projekte", "projectSelector.projectsHeader": "Projekte",

View File

@@ -411,6 +411,26 @@
"editor.footer.created": "Created", "editor.footer.created": "Created",
"editor.footer.updated": "Updated", "editor.footer.updated": "Updated",
"editor.footer.published": "Published", "editor.footer.published": "Published",
"dashboard.title": "Dashboard",
"dashboard.subtitle": "Overview of your blog database",
"dashboard.stats.totalPosts": "Total Posts",
"dashboard.stats.published": "{count} published",
"dashboard.stats.drafts": "{count} drafts",
"dashboard.stats.archived": "{count} archived",
"dashboard.stats.mediaFiles": "Media Files",
"dashboard.stats.images": "{count} images",
"dashboard.stats.tags": "Tags",
"dashboard.stats.categories": "{count} categories",
"dashboard.section.postsOverTime": "Posts Over Time",
"dashboard.section.tags": "Tags",
"dashboard.section.categories": "Categories",
"dashboard.section.recentlyUpdated": "Recently Updated",
"dashboard.tagCloud.more": "+{count} more",
"dashboard.postCount.one": "{count} post",
"dashboard.postCount.other": "{count} posts",
"dashboard.status.draft": "Draft",
"dashboard.status.published": "Published",
"dashboard.status.archived": "Archived",
"projectSelector.switchProject": "Switch project", "projectSelector.switchProject": "Switch project",
"projectSelector.selectProject": "Select project", "projectSelector.selectProject": "Select project",
"projectSelector.projectsHeader": "Projects", "projectSelector.projectsHeader": "Projects",

View File

@@ -411,6 +411,26 @@
"editor.footer.created": "Creado", "editor.footer.created": "Creado",
"editor.footer.updated": "Actualizado", "editor.footer.updated": "Actualizado",
"editor.footer.published": "Publicado", "editor.footer.published": "Publicado",
"dashboard.title": "Panel",
"dashboard.subtitle": "Resumen de la base de datos de tu blog",
"dashboard.stats.totalPosts": "Entradas totales",
"dashboard.stats.published": "{count} publicadas",
"dashboard.stats.drafts": "{count} borradores",
"dashboard.stats.archived": "{count} archivadas",
"dashboard.stats.mediaFiles": "Archivos multimedia",
"dashboard.stats.images": "{count} imágenes",
"dashboard.stats.tags": "Etiquetas",
"dashboard.stats.categories": "{count} categorías",
"dashboard.section.postsOverTime": "Entradas a lo largo del tiempo",
"dashboard.section.tags": "Etiquetas",
"dashboard.section.categories": "Categorías",
"dashboard.section.recentlyUpdated": "Actualizadas recientemente",
"dashboard.tagCloud.more": "+{count} más",
"dashboard.postCount.one": "{count} entrada",
"dashboard.postCount.other": "{count} entradas",
"dashboard.status.draft": "Borrador",
"dashboard.status.published": "Publicada",
"dashboard.status.archived": "Archivada",
"projectSelector.switchProject": "Cambiar proyecto", "projectSelector.switchProject": "Cambiar proyecto",
"projectSelector.selectProject": "Seleccionar proyecto", "projectSelector.selectProject": "Seleccionar proyecto",
"projectSelector.projectsHeader": "Proyectos", "projectSelector.projectsHeader": "Proyectos",

View File

@@ -411,6 +411,26 @@
"editor.footer.created": "Créé", "editor.footer.created": "Créé",
"editor.footer.updated": "Mis à jour", "editor.footer.updated": "Mis à jour",
"editor.footer.published": "Publié", "editor.footer.published": "Publié",
"dashboard.title": "Tableau de bord",
"dashboard.subtitle": "Aperçu de la base de données de votre blog",
"dashboard.stats.totalPosts": "Articles au total",
"dashboard.stats.published": "{count} publiés",
"dashboard.stats.drafts": "{count} brouillons",
"dashboard.stats.archived": "{count} archivés",
"dashboard.stats.mediaFiles": "Fichiers média",
"dashboard.stats.images": "{count} images",
"dashboard.stats.tags": "Étiquettes",
"dashboard.stats.categories": "{count} catégories",
"dashboard.section.postsOverTime": "Articles dans le temps",
"dashboard.section.tags": "Étiquettes",
"dashboard.section.categories": "Catégories",
"dashboard.section.recentlyUpdated": "Récemment mis à jour",
"dashboard.tagCloud.more": "+{count} de plus",
"dashboard.postCount.one": "{count} article",
"dashboard.postCount.other": "{count} articles",
"dashboard.status.draft": "Brouillon",
"dashboard.status.published": "Publié",
"dashboard.status.archived": "Archivé",
"projectSelector.switchProject": "Changer de projet", "projectSelector.switchProject": "Changer de projet",
"projectSelector.selectProject": "Sélectionner un projet", "projectSelector.selectProject": "Sélectionner un projet",
"projectSelector.projectsHeader": "Projets", "projectSelector.projectsHeader": "Projets",

View File

@@ -411,6 +411,26 @@
"editor.footer.created": "Creato", "editor.footer.created": "Creato",
"editor.footer.updated": "Aggiornato", "editor.footer.updated": "Aggiornato",
"editor.footer.published": "Pubblicato", "editor.footer.published": "Pubblicato",
"dashboard.title": "Dashboard",
"dashboard.subtitle": "Panoramica del database del tuo blog",
"dashboard.stats.totalPosts": "Post totali",
"dashboard.stats.published": "{count} pubblicati",
"dashboard.stats.drafts": "{count} bozze",
"dashboard.stats.archived": "{count} archiviati",
"dashboard.stats.mediaFiles": "File multimediali",
"dashboard.stats.images": "{count} immagini",
"dashboard.stats.tags": "Tag",
"dashboard.stats.categories": "{count} categorie",
"dashboard.section.postsOverTime": "Post nel tempo",
"dashboard.section.tags": "Tag",
"dashboard.section.categories": "Categorie",
"dashboard.section.recentlyUpdated": "Aggiornati di recente",
"dashboard.tagCloud.more": "+{count} in più",
"dashboard.postCount.one": "{count} post",
"dashboard.postCount.other": "{count} post",
"dashboard.status.draft": "Bozza",
"dashboard.status.published": "Pubblicato",
"dashboard.status.archived": "Archiviato",
"projectSelector.switchProject": "Cambia progetto", "projectSelector.switchProject": "Cambia progetto",
"projectSelector.selectProject": "Seleziona progetto", "projectSelector.selectProject": "Seleziona progetto",
"projectSelector.projectsHeader": "Progetti", "projectSelector.projectsHeader": "Progetti",