Add i18n support
This commit is contained in:
46
index.html
46
index.html
@@ -9,13 +9,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="automaton-selector">
|
||||
<h1>Select an automaton</h1>
|
||||
<h1 data-i18n>Select an automaton</h1>
|
||||
<div id="automaton-collection"></div>
|
||||
</div>
|
||||
<div id="app" hidden="hidden">
|
||||
<div class="actions">
|
||||
<button id="back-button">Back</button>
|
||||
<a id="controls-button" class="button open-modal" href="#controls">Controls</a>
|
||||
<button id="back-button" data-i18n>Back</button>
|
||||
<a id="controls-button" class="button open-modal" href="#controls" data-i18n>Controls</a>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input type="text" id="word-input" autocapitalize="off" spellcheck="false" />
|
||||
@@ -23,52 +23,52 @@
|
||||
</div>
|
||||
<div class="input">
|
||||
<div id="input-buttons"></div>
|
||||
<button id="clear-button">Clear</button>
|
||||
<button id="clear-button" data-i18n>Clear</button>
|
||||
</div>
|
||||
<div id="state-graph"></div>
|
||||
</div>
|
||||
<div id="controls" class="modal" hidden="hidden">
|
||||
<div class="modal-content">
|
||||
<h3>Controls</h3>
|
||||
<h3 data-i18n>Controls</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Action</th>
|
||||
<th>Desktop</th>
|
||||
<th data-i18n>Action</th>
|
||||
<th data-i18n>Desktop</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Pan</td>
|
||||
<td>Left-click and drag</td>
|
||||
<td data-i18n>Pan</td>
|
||||
<td data-i18n>Left-click and drag</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Zoom in/out</td>
|
||||
<td>Mouse wheel</td>
|
||||
<td data-i18n>Zoom in/out</td>
|
||||
<td data-i18n>Mouse wheel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create state</td>
|
||||
<td>Double-click</td>
|
||||
<td data-i18n>Create state</td>
|
||||
<td data-i18n>Double-click</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Move state</td>
|
||||
<td>Middle-click and drag</td>
|
||||
<td data-i18n>Move state</td>
|
||||
<td data-i18n>Middle-click and drag</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Edit/Delete state</td>
|
||||
<td>Right-click</td>
|
||||
<td data-i18n>Edit/Delete state</td>
|
||||
<td data-i18n>Right-click</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create transition</td>
|
||||
<td>Left-click and drag</td>
|
||||
<td data-i18n>Create transition</td>
|
||||
<td data-i18n>Left-click and drag</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Edit transition</td>
|
||||
<td>Right-click</td>
|
||||
<td data-i18n>Edit transition</td>
|
||||
<td data-i18n>Right-click</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete transition</td>
|
||||
<td>Right-click</td>
|
||||
<td data-i18n>Delete transition</td>
|
||||
<td data-i18n>Right-click</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
@@ -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
51
src/i18n.ts
Normal 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;
|
||||
}
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user