i18n: translate hardcoded strings on project page and footer
Build and Deploy Verso / deploy (push) Successful in 17m39s

- Card owner: "You" now goes through t('you') so it renders as
  "Vous"/"Tu"/"Du" etc. instead of always "You"
- Relative dates (fromNow): moment.locale() is now set from the app
  language so "2 days ago" becomes "il y a 2 jours" etc.
- Footer: "Built on", "Source code", "AGPL licence" are now translated
  via t() with keys added to all locale files and extracted-translations
- New keys: built_on, source_code, agpl_licence (FR/DE/IT/ES translations
  added manually)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-13 09:33:22 +00:00
parent 093c25f3dd
commit ae4e95312d
9 changed files with 36 additions and 4 deletions
@@ -117,6 +117,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "",
"aggregate_changed": "",
"aggregate_to": "",
"agpl_licence": "",
"agree": "",
"agree_with_the_terms": "",
"ai_assist_in_overleaf_is_included_via_writefull_groups": "",
@@ -228,6 +229,7 @@
"breadcrumbs": "",
"browser": "",
"build_collection_of_most_used_references": "",
"built_on": "",
"bullet_list": "",
"buy_licenses": "",
"buy_more_licenses": "",
@@ -1909,6 +1911,7 @@
"sort_by": "",
"sort_by_x": "",
"sort_projects": "",
"source_code": "",
"speak": "",
"speech_input_not_available": "",
"spellcheck": "",
@@ -99,9 +99,11 @@ const ProjectCard = memo(function ProjectCard({
}: {
project: Project
}) {
const { t } = useTranslation()
const { tags } = useProjectListContext()
const variant = getFormatVariant(project.compiler, project.quartoFlavor)
const ownerName = getOwnerName(project)
const rawOwner = getOwnerName(project)
const ownerName = rawOwner === 'You' ? t('you') : rawOwner
const date = fromNowDate(project.lastUpdated)
const initial = project.name.charAt(0).toUpperCase() || '?'
const projectTags = tags
@@ -5,6 +5,7 @@ import type {
import OLRow from '@/shared/components/ol/ol-row'
import LanguagePicker from '@/shared/components/language-picker'
import React from 'react'
import { useTranslation } from 'react-i18next'
function FooterItemLi({
text,
@@ -41,6 +42,7 @@ function Separator() {
}
function ThinFooter({ subdomainLang, leftItems, rightItems }: FooterMetadata) {
const { t } = useTranslation()
const showLanguagePicker = Boolean(
subdomainLang && Object.keys(subdomainLang).length > 1
)
@@ -62,7 +64,7 @@ function ThinFooter({ subdomainLang, leftItems, rightItems }: FooterMetadata) {
</li>
<Separator />
<li>
Built on{' '}
{t('built_on')}{' '}
<a
href="https://github.com/overleaf/overleaf"
target="_blank"
@@ -90,7 +92,7 @@ function ThinFooter({ subdomainLang, leftItems, rightItems }: FooterMetadata) {
target="_blank"
rel="noopener noreferrer"
>
AGPL licence
{t('agpl_licence')}
</a>
</li>
<Separator />
@@ -100,7 +102,7 @@ function ThinFooter({ subdomainLang, leftItems, rightItems }: FooterMetadata) {
target="_blank"
rel="noopener noreferrer"
>
Source code
{t('source_code')}
</a>
</li>
{rightItems?.map(item => (
+10
View File
@@ -1,4 +1,14 @@
import moment from 'moment'
import getMeta from '@/utils/meta'
// Set moment's display locale to match the app language so that
// fromNow() returns "il y a 2 jours" rather than "2 days ago", etc.
const _lang = getMeta('ol-i18n')?.currentLangCode ?? 'en'
if (_lang !== 'en') {
import(`moment/locale/${_lang}`)
.then(() => { moment.locale(_lang) })
.catch(() => { /* fall back to English */ })
}
export function formatDate(date: moment.MomentInput, format?: string) {
if (!date) return 'N/A'
+3
View File
@@ -146,6 +146,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "Danach berechnen wir dir jährlich am __date__ __totalAmount__ (__subtotalAmount__ + __taxAmount__ Steuern), falls du nicht kündigst.",
"aggregate_changed": "Geändert",
"aggregate_to": "zu",
"agpl_licence": "AGPL-Lizenz",
"agree": "Zustimmen",
"agree_with_the_terms": "Ich stimme den Verso Bedingungen zu",
"ai_allowance": "KI-Zulage",
@@ -311,6 +312,7 @@
"browser": "Browser",
"build_collection_of_most_used_references": "Erstellen Sie in der Bibliothek eine Sammlung Ihrer am häufigsten verwendeten Referenzen, damit Sie sie problemlos zu jedem Projekt hinzufügen können.",
"built_in": "Eigener",
"built_on": "Basiert auf",
"bullet_list": "Bullet-Liste",
"buy_add_on": "Add-on kaufen",
"buy_licenses": "Lizenzen kaufen",
@@ -2495,6 +2497,7 @@
"sort_by_x": "Nach __x__ sortieren",
"sort_projects": "Projekte sortieren",
"source": "Quelldateien",
"source_code": "Quellcode",
"speak": "Sprich",
"speech_input_not_available": "Die Spracheingabe ist in diesem Browser noch nicht verfügbar",
"spellcheck": "Rechtschreibprüfung",
+3
View File
@@ -143,6 +143,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "After that, well bill you __totalAmount__ (__subtotalAmount__ + __taxAmount__ tax) annually on __date__, unless you cancel.",
"aggregate_changed": "Changed",
"aggregate_to": "to",
"agpl_licence": "AGPL licence",
"agree": "Agree",
"agree_with_the_terms": "I agree with the Verso terms",
"ai_allowance": "AI allowance",
@@ -305,6 +306,7 @@
"browser": "Browser",
"build_collection_of_most_used_references": "Build a collection of your most-used references in the Library, so you can easily add them to any project.",
"built_in": "Built-In",
"built_on": "Built on",
"bullet_list": "Bullet list",
"buy_add_on": "Buy add-on",
"buy_licenses": "Buy licenses",
@@ -2474,6 +2476,7 @@
"sort_by_x": "Sort by __x__",
"sort_projects": "Sort projects",
"source": "Source",
"source_code": "Source code",
"speak": "Speak",
"speech_input_not_available": "Speech input is not yet available in this browser",
"spellcheck": "Spellcheck",
+3
View File
@@ -143,6 +143,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "Después de eso, le facturaremos __totalAmount__ (__subtotalAmount__ + __taxAmount__ impuestos) anualmente el __date__, a menos que cancele.",
"aggregate_changed": "Cambiado",
"aggregate_to": "a",
"agpl_licence": "Licencia AGPL",
"agree": "De acuerdo",
"agree_with_the_terms": "Estoy de acuerdo con los términos Verso",
"ai_allowance": "subsidio de IA",
@@ -306,6 +307,7 @@
"browser": "Navegador",
"build_collection_of_most_used_references": "Cree una colección de sus referencias más utilizadas en la Biblioteca, para que pueda agregarlas fácilmente a cualquier proyecto.",
"built_in": "Integrado",
"built_on": "Desarrollado con",
"bullet_list": "lista de viñetas",
"buy_add_on": "Comprar complemento",
"buy_licenses": "Comprar licencias",
@@ -2475,6 +2477,7 @@
"sort_by_x": "Ordenar por __x__",
"sort_projects": "Ordenar proyectos",
"source": "Fuente",
"source_code": "Código fuente",
"speak": "hablar",
"speech_input_not_available": "La entrada de voz aún no está disponible en este navegador",
"spellcheck": "corrector ortográfico",
+3
View File
@@ -143,6 +143,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "Après cela, nous vous facturerons __totalAmount__ (__subtotalAmount__ + __taxAmount__ taxe) annuellement le __date__, sauf si vous annulez.",
"aggregate_changed": "Modification de",
"aggregate_to": "en",
"agpl_licence": "Licence AGPL",
"agree": "D'accord",
"agree_with_the_terms": "J'accepte les conditions d'utilisation de Verso",
"ai_allowance": "Allocation IA",
@@ -306,6 +307,7 @@
"browser": "Navigateur",
"build_collection_of_most_used_references": "Créez une collection de vos références les plus utilisées dans la bibliothèque, afin de pouvoir les ajouter facilement à n'importe quel projet.",
"built_in": "Intégré",
"built_on": "Construit sur",
"bullet_list": "Liste à puces",
"buy_add_on": "Acheter un module complémentaire",
"buy_licenses": "Acheter des licences",
@@ -2479,6 +2481,7 @@
"sort_by_x": "Trier par __x__",
"sort_projects": "Trier les projets",
"source": "Code source",
"source_code": "Code source",
"speak": "Parler",
"speech_input_not_available": "La saisie vocale n'est pas encore disponible dans ce navigateur",
"spellcheck": "Vérification orthographique",
+3
View File
@@ -143,6 +143,7 @@
"after_that_well_bill_you_x_total_y_subtotal_z_tax_annually_on_date_unless_you_cancel": "Successivamente, ti addebiteremo __totalAmount__ (__subtotalAmount__ + __taxAmount__ tasse) annualmente il __date__, a meno che tu non annulli l'abbonamento.",
"aggregate_changed": "Cambiato",
"aggregate_to": "a",
"agpl_licence": "Licenza AGPL",
"agree": "D'accordo",
"agree_with_the_terms": "Sono d'accordo con i termini di Verso",
"ai_allowance": "Indennità AI",
@@ -305,6 +306,7 @@
"browser": "Navigatore",
"build_collection_of_most_used_references": "Crea una raccolta dei riferimenti più utilizzati nella Libreria, in modo da poterli aggiungere facilmente a qualsiasi progetto.",
"built_in": "Built-In",
"built_on": "Basato su",
"bullet_list": "Elenco puntato",
"buy_add_on": "Acquista il componente aggiuntivo",
"buy_licenses": "Acquista licenze",
@@ -2474,6 +2476,7 @@
"sort_by_x": "Ordina per __x__",
"sort_projects": "Ordina progetti",
"source": "Sorgente",
"source_code": "Codice sorgente",
"speak": "Parla",
"speech_input_not_available": "L'input vocale non è ancora disponibile in questo browser",
"spellcheck": "Controllo ortografico",