diff --git a/index.html b/index.html
index 175eea5..9efa8fe 100644
--- a/index.html
+++ b/index.html
@@ -9,13 +9,13 @@
-
Controls
+
Controls
- Action |
- Desktop |
+ Action |
+ Desktop |
- Pan |
- Left-click and drag |
+ Pan |
+ Left-click and drag |
- Zoom in/out |
- Mouse wheel |
+ Zoom in/out |
+ Mouse wheel |
- Create state |
- Double-click |
+ Create state |
+ Double-click |
- Move state |
- Middle-click and drag |
+ Move state |
+ Middle-click and drag |
- Edit/Delete state |
- Right-click |
+ Edit/Delete state |
+ Right-click |
- Create transition |
- Left-click and drag |
+ Create transition |
+ Left-click and drag |
- Edit transition |
- Right-click |
+ Edit transition |
+ Right-click |
- Delete transition |
- Right-click |
+ Delete transition |
+ Right-click |
diff --git a/src/editor/GraphEditor.ts b/src/editor/GraphEditor.ts
index e368b0a..cb41cb3 100644
--- a/src/editor/GraphEditor.ts
+++ b/src/editor/GraphEditor.ts
@@ -1,5 +1,6 @@
import * as d3 from 'd3';
import { D3DragEvent, D3ZoomEvent } from 'd3';
+import { tr } from '../i18n.ts';
import { Canvas, createCanvas, createSimulation, createZoom, GraphConfiguration, Simulation } from './d3/canvas.ts';
import { Graph, Link, Node } from './d3/graph.ts';
import { initMarkers } from './d3/markers.ts';
@@ -235,17 +236,17 @@ export class GraphEditor extends HTMLElement {
if (!this.readonly) {
nodeGroup.on('contextmenu', (event: MouseEvent, d: Node) => {
this.moveMenu(event, [
- new MenuAction('Start', () => d.start, () => {
+ new MenuAction(tr('Start'), () => d.start, () => {
d.start = !d.start;
this.fireOnChange();
this.restart();
}),
- new MenuAction('Accepting', () => d.accepting, () => {
+ new MenuAction(tr('Accepting'), () => d.accepting, () => {
d.accepting = !d.accepting;
this.fireOnChange();
this.restart();
}),
- new MenuAction('Delete', () => false, () => {
+ new MenuAction(tr('Delete'), () => false, () => {
this.graph.removeNode(d);
this.fireOnChange();
this.resetDraggableLink();
diff --git a/src/i18n.ts b/src/i18n.ts
new file mode 100644
index 0000000..e5d6511
--- /dev/null
+++ b/src/i18n.ts
@@ -0,0 +1,51 @@
+const fr = {
+ 'Select an automaton': 'Sélectionner un automate',
+ 'Back': 'Retour',
+ 'Clear': 'Effacer',
+ 'Delete': 'Supprimer',
+ 'Controls': 'Contrôles',
+ 'Action': 'Action',
+ 'Desktop': 'Bureau',
+ 'Pan': 'Déplacer',
+ 'Zoom in/out': 'Zoomer',
+ 'Create state': 'Créer un état',
+ 'Move state': 'Déplacer un état',
+ 'Edit/Delete state': 'Éditer/Supprimer un état',
+ 'Create transition': 'Créer une transition',
+ 'Edit transition': 'Éditer une transition',
+ 'Delete transition': 'Supprimer une transition',
+ 'Left-click and drag': 'Clic gauche et déplacer',
+ 'Mouse wheel': 'Molette de la souris',
+ 'Double-click': 'Double-clic',
+ 'Middle-click and drag': 'Clic molette et déplacer',
+ 'Right-click': 'Clic droit',
+ 'Starts and ends with a': 'Commence et finit par a',
+ 'Starts with bb': 'Commence par bb',
+ 'Exactly one b': 'Exactement un b',
+ 'Odd number of a': 'Nombre impair de a',
+ 'Ends with two b': 'Finit par deux b',
+ 'states': 'états',
+ 'Create New Automaton': 'Créer un nouvel automate',
+ 'Create a new automaton from scratch.': 'Créer un nouvel automate à partir de zéro.',
+ 'Start': 'Début',
+ 'Accepting': 'Acceptant',
+};
+type TranslationKey = keyof typeof fr;
+
+let lang = 'en-US';
+if (navigator.languages) {
+ lang = navigator.languages.find((l) => l !== lang) || lang;
+}
+
+const messages: Record
= lang.startsWith('fr') ? fr : {};
+document.querySelectorAll('[data-i18n]').forEach((element) => {
+ element.textContent = trUserContent(element.textContent!);
+});
+
+export function tr(key: TranslationKey): string {
+ return messages[key] || key;
+}
+
+export function trUserContent(key: string): string {
+ return messages[key] || key;
+}
diff --git a/src/main.js b/src/main.js
index 1a1bafb..318a09d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,6 @@
import { AUTOMATONS } from './examples.js';
import { openAutomaton } from './fsm.js';
+import { tr, trUserContent } from './i18n.ts';
import './modal.ts';
const automatonSelector = /** @type {HTMLDivElement} */ (document.getElementById('automaton-selector'));
@@ -14,8 +15,8 @@ for (const [displayName, automaton] of Object.entries(AUTOMATONS)) {
card.classList.add('card');
card.innerHTML = `
-
${displayName}
-
${automaton.length} states
+
${trUserContent(displayName)}
+
${automaton.length} ${tr('states')}
`;
const handleEvent = () => {
@@ -35,8 +36,8 @@ const create = document.createElement('div');
create.classList.add('card');
create.innerHTML = `
-
Create New Automaton
-
Create a new automaton from scratch.
+
${tr('Create New Automaton')}
+
${tr('Create a new automaton from scratch.')}
`;
automatonCollection.appendChild(create);