Add i18n support

This commit is contained in:
2024-03-05 21:10:52 +01:00
parent 24b38a2eac
commit bc3cac2460
4 changed files with 83 additions and 30 deletions

View File

@@ -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();

51
src/i18n.ts Normal file
View File

@@ -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<string, string> = 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;
}

View File

@@ -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 = `
<div class="card-body">
<h5 class="card-title">${displayName}</h5>
<p class="card-text">${automaton.length} states</p>
<h5 class="card-title">${trUserContent(displayName)}</h5>
<p class="card-text">${automaton.length} ${tr('states')}</p>
</div>
`;
const handleEvent = () => {
@@ -35,8 +36,8 @@ const create = document.createElement('div');
create.classList.add('card');
create.innerHTML = `
<div class="card-body">
<h5 class="card-title">Create New Automaton</h5>
<p class="card-text">Create a new automaton from scratch.</p>
<h5 class="card-title">${tr('Create New Automaton')}</h5>
<p class="card-text">${tr('Create a new automaton from scratch.')}</p>
</div>
`;
automatonCollection.appendChild(create);