({
type="radio"
value={value as string}
onChange={() => onChange(value)}
- title={capitalize(value as string)}
+ title={alignmentLabel(value as string)}
{...props}
/>
diff --git a/visual-editor/src/functions/i18n.ts b/visual-editor/src/functions/i18n.ts
index 4f4f8db4..d7afd325 100644
--- a/visual-editor/src/functions/i18n.ts
+++ b/visual-editor/src/functions/i18n.ts
@@ -1,6 +1,28 @@
import type { TranslationKey } from 'src/types'
import { VisualEditor } from 'src/VisualEditor'
+import { Translations as EN } from 'src/langs/en'
+/**
+ * Falls back to English when the host translation lacks a key
+ * (translations written for an older version)
+ */
export function t(key: TranslationKey): string {
- return VisualEditor.i18n[key]
+ return VisualEditor.i18n[key] ?? EN[key]
+}
+
+const alignmentKeys: Record = {
+ left: 'alignLeft',
+ center: 'alignCenter',
+ right: 'alignRight',
+ justify: 'alignJustify',
+ top: 'alignTop',
+ bottom: 'alignBottom',
+}
+
+/**
+ * Label of an alignment value (left, center, right...)
+ */
+export function alignmentLabel(value: string): string {
+ const key = alignmentKeys[value]
+ return key ? t(key) : value
}
diff --git a/visual-editor/src/hooks/useAsyncEffect.ts b/visual-editor/src/hooks/useAsyncEffect.ts
deleted file mode 100644
index 0c47324b..00000000
--- a/visual-editor/src/hooks/useAsyncEffect.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { useEffect, useState } from 'react'
-
-export function useAsyncEffect(cb: Function, deps: unknown[]) {
- useEffect(() => {
- cb()
- }, deps)
-}
diff --git a/visual-editor/src/langs/en.ts b/visual-editor/src/langs/en.ts
index 2e2b1e1b..01e25566 100644
--- a/visual-editor/src/langs/en.ts
+++ b/visual-editor/src/langs/en.ts
@@ -19,4 +19,35 @@ export const Translations: Translation = {
noContent: `You have no content yet`,
useTemplate: 'Use a template',
poweredBy: 'Powered by',
+ allBlocks: 'All blocks',
+ moveUp: 'Move up',
+ moveDown: 'Move down',
+ deviceMobile: 'Mobile',
+ deviceDesktop: 'Desktop',
+ alignLeft: 'Left',
+ alignCenter: 'Center',
+ alignRight: 'Right',
+ alignJustify: 'Justify',
+ alignTop: 'Top',
+ alignBottom: 'Bottom',
+ orderedList: 'Ordered list',
+ bulletList: 'Bullet list',
+ blockquote: 'Quote',
+ heading: 'Heading',
+ bold: 'Bold',
+ italic: 'Italic',
+ underline: 'Underline',
+ highlight: 'Highlight',
+ strike: 'Strikethrough',
+ superscript: 'Superscript',
+ subscript: 'Subscript',
+ code: 'Code',
+ color: 'Color',
+ noColor: 'No color',
+ link: 'Link',
+ clearFormat: 'Clear formatting',
+ linkNewTab: 'New tab',
+ linkNewTabTitle: 'Open the link in a new tab',
+ linkSubmit: 'Ok',
+ linkInvalid: 'Invalid URL, add "https://"',
}
diff --git a/visual-editor/src/langs/fr.ts b/visual-editor/src/langs/fr.ts
index bc826a08..7f291e45 100644
--- a/visual-editor/src/langs/fr.ts
+++ b/visual-editor/src/langs/fr.ts
@@ -17,4 +17,35 @@ export const Translations = {
poweredBy: 'Propulsé par',
noContent: `Vous n'avez pas encore de contenu`,
useTemplate: 'Utiliser un modèle',
+ allBlocks: 'Tous les blocs',
+ moveUp: 'Monter le bloc',
+ moveDown: 'Descendre le bloc',
+ deviceMobile: 'Mobile',
+ deviceDesktop: 'Ordinateur',
+ alignLeft: 'Gauche',
+ alignCenter: 'Centre',
+ alignRight: 'Droite',
+ alignJustify: 'Justifié',
+ alignTop: 'Haut',
+ alignBottom: 'Bas',
+ orderedList: 'Liste numérotée',
+ bulletList: 'Liste à puces',
+ blockquote: 'Citation',
+ heading: 'Titre',
+ bold: 'Gras',
+ italic: 'Italique',
+ underline: 'Souligné',
+ highlight: 'Surligné',
+ strike: 'Barré',
+ superscript: 'Exposant',
+ subscript: 'Indice',
+ code: 'Code',
+ color: 'Couleur',
+ noColor: 'Aucune couleur',
+ link: 'Lien',
+ clearFormat: 'Effacer la mise en forme',
+ linkNewTab: 'Nouvel onglet',
+ linkNewTabTitle: 'Ouvrir le lien dans un nouvel onglet',
+ linkSubmit: 'OK',
+ linkInvalid: 'Adresse invalide, ajoutez "https://"',
}
diff --git a/visual-editor/test/functions/i18n.test.ts b/visual-editor/test/functions/i18n.test.ts
new file mode 100644
index 00000000..f326649c
--- /dev/null
+++ b/visual-editor/test/functions/i18n.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, it } from 'vitest'
+import { Translations as EN } from 'src/langs/en'
+import { Translations as FR } from 'src/langs/fr'
+import { VisualEditor } from 'src/VisualEditor'
+import { alignmentLabel, t } from 'src/functions/i18n'
+
+describe('translations', () => {
+ it('has the same keys in English and French', () => {
+ expect(Object.keys(FR).sort()).toEqual(Object.keys(EN).sort())
+ })
+
+ it('uses English by default', () => {
+ new VisualEditor()
+ expect(t('linkNewTab')).toBe('New tab')
+ expect(VisualEditor.devices.map((d) => d.name)).toEqual([
+ 'Mobile',
+ 'Desktop',
+ ])
+ })
+
+ it('uses the language given to the editor', () => {
+ new VisualEditor({ lang: FR })
+ expect(t('linkNewTab')).toBe('Nouvel onglet')
+ expect(alignmentLabel('center')).toBe('Centre')
+ expect(VisualEditor.devices.map((d) => d.name)).toEqual([
+ 'Mobile',
+ 'Ordinateur',
+ ])
+ })
+
+ it('falls back to English for a key missing in the host translation', () => {
+ const { linkNewTab, ...partial } = FR
+ new VisualEditor({ lang: partial as typeof FR })
+ expect(t('linkNewTab')).toBe('New tab')
+ expect(t('save')).toBe('Sauvegarder')
+ })
+})