Files
valgo/lib/i18n.ts
2026-07-05 23:03:23 +02:00

444 lines
29 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ActionType, AlgorithmKey, LanguageCode, Step } from "./types";
export type UIKey =
| "brand"
| "tagline"
| "play"
| "pause"
| "resume"
| "reset"
| "shuffle"
| "algorithm"
| "language"
| "arraySize"
| "elements"
| "speed"
| "speedSlow"
| "speedNormal"
| "speedFast"
| "speedTurbo"
| "comparisons"
| "writes"
| "step"
| "of"
| "sorted"
| "license"
| "viewLicense"
| "close"
| "footerRights"
| "growthTitle"
| "growthCaption"
| "narrationTitle"
| "statsTitle"
| "pickAlgorithm"
| "done";
const UI: Record<LanguageCode, Record<UIKey, string>> = {
en: {
brand: "Valgo",
tagline: "Watch how sorting actually works.",
play: "Play", pause: "Pause", resume: "Resume", reset: "New shuffle",
shuffle: "Shuffle", algorithm: "Algorithm", language: "Language",
arraySize: "Array size", elements: "elements",
speed: "Speed", speedSlow: "Slow", speedNormal: "Normal", speedFast: "Fast", speedTurbo: "Turbo",
comparisons: "Comparisons", writes: "Writes", step: "Step", of: "of", sorted: "Sorted",
license: "MIT License", viewLicense: "License", close: "Close",
footerRights: "All rights reserved.",
growthTitle: "How the work grows", growthCaption: "Same n, wildly different amounts of work.",
narrationTitle: "What's happening", statsTitle: "Live stats",
pickAlgorithm: "Choose an algorithm", done: "Sorted! Every value found its place.",
},
fr: {
brand: "Valgo",
tagline: "Regardez comment le tri fonctionne vraiment.",
play: "Lecture", pause: "Pause", resume: "Reprendre", reset: "Nouveau mélange",
shuffle: "Mélanger", algorithm: "Algorithme", language: "Langue",
arraySize: "Taille du tableau", elements: "éléments",
speed: "Vitesse", speedSlow: "Lent", speedNormal: "Normal", speedFast: "Rapide", speedTurbo: "Turbo",
comparisons: "Comparaisons", writes: "Écritures", step: "Étape", of: "sur", sorted: "Triés",
license: "Licence MIT", viewLicense: "Licence", close: "Fermer",
footerRights: "Tous droits réservés.",
growthTitle: "Comment le travail augmente", growthCaption: "Même n, quantités de travail très différentes.",
narrationTitle: "Ce qui se passe", statsTitle: "Statistiques en direct",
pickAlgorithm: "Choisir un algorithme", done: "Trié ! Chaque valeur a trouvé sa place.",
},
it: {
brand: "Valgo",
tagline: "Guarda come funziona davvero l'ordinamento.",
play: "Avvia", pause: "Pausa", resume: "Riprendi", reset: "Nuovo mescolamento",
shuffle: "Mescola", algorithm: "Algoritmo", language: "Lingua",
arraySize: "Dimensione array", elements: "elementi",
speed: "Velocità", speedSlow: "Lenta", speedNormal: "Normale", speedFast: "Veloce", speedTurbo: "Turbo",
comparisons: "Confronti", writes: "Scritture", step: "Passo", of: "di", sorted: "Ordinati",
license: "Licenza MIT", viewLicense: "Licenza", close: "Chiudi",
footerRights: "Tutti i diritti riservati.",
growthTitle: "Come cresce il lavoro", growthCaption: "Stesso n, quantità di lavoro molto diverse.",
narrationTitle: "Cosa sta succedendo", statsTitle: "Statistiche live",
pickAlgorithm: "Scegli un algoritmo", done: "Ordinato! Ogni valore ha trovato il suo posto.",
},
de: {
brand: "Valgo",
tagline: "Sieh dir an, wie Sortieren wirklich funktioniert.",
play: "Abspielen", pause: "Pause", resume: "Fortsetzen", reset: "Neu mischen",
shuffle: "Mischen", algorithm: "Algorithmus", language: "Sprache",
arraySize: "Array-Größe", elements: "Elemente",
speed: "Geschwindigkeit", speedSlow: "Langsam", speedNormal: "Normal", speedFast: "Schnell", speedTurbo: "Turbo",
comparisons: "Vergleiche", writes: "Schreibvorgänge", step: "Schritt", of: "von", sorted: "Sortiert",
license: "MIT-Lizenz", viewLicense: "Lizenz", close: "Schließen",
footerRights: "Alle Rechte vorbehalten.",
growthTitle: "Wie die Arbeit wächst", growthCaption: "Gleiches n, sehr unterschiedlicher Aufwand.",
narrationTitle: "Was gerade passiert", statsTitle: "Live-Statistik",
pickAlgorithm: "Algorithmus wählen", done: "Fertig sortiert! Jeder Wert hat seinen Platz gefunden.",
},
es: {
brand: "Valgo",
tagline: "Mira cómo funciona realmente el ordenamiento.",
play: "Reproducir", pause: "Pausa", resume: "Reanudar", reset: "Nueva mezcla",
shuffle: "Mezclar", algorithm: "Algoritmo", language: "Idioma",
arraySize: "Tamaño del array", elements: "elementos",
speed: "Velocidad", speedSlow: "Lenta", speedNormal: "Normal", speedFast: "Rápida", speedTurbo: "Turbo",
comparisons: "Comparaciones", writes: "Escrituras", step: "Paso", of: "de", sorted: "Ordenados",
license: "Licencia MIT", viewLicense: "Licencia", close: "Cerrar",
footerRights: "Todos los derechos reservados.",
growthTitle: "Cómo crece el trabajo", growthCaption: "Mismo n, cantidades de trabajo muy distintas.",
narrationTitle: "Qué está pasando", statsTitle: "Estadísticas en vivo",
pickAlgorithm: "Elige un algoritmo", done: "¡Ordenado! Cada valor encontró su lugar.",
},
ja: {
brand: "Valgo",
tagline: "ソートが実際にどう動くかを見てみましょう。",
play: "再生", pause: "一時停止", resume: "再開", reset: "シャッフル",
shuffle: "シャッフル", algorithm: "アルゴリズム", language: "言語",
arraySize: "配列サイズ", elements: "要素",
speed: "速度", speedSlow: "遅い", speedNormal: "普通", speedFast: "速い", speedTurbo: "高速",
comparisons: "比較回数", writes: "書き込み回数", step: "ステップ", of: "/", sorted: "ソート済み",
license: "MITライセンス", viewLicense: "ライセンス", close: "閉じる",
footerRights: "全著作権所有。",
growthTitle: "処理量の増え方", growthCaption: "同じ n でも、処理量は大きく異なります。",
narrationTitle: "今起きていること", statsTitle: "リアルタイム統計",
pickAlgorithm: "アルゴリズムを選択", done: "完了!すべての値が正しい位置に収まりました。",
},
no: {
brand: "Valgo",
tagline: "Se hvordan sortering faktisk fungerer.",
play: "Spill av", pause: "Pause", resume: "Fortsett", reset: "Stokk på nytt",
shuffle: "Stokk", algorithm: "Algoritme", language: "Språk",
arraySize: "Matrisestørrelse", elements: "elementer",
speed: "Hastighet", speedSlow: "Sakte", speedNormal: "Normal", speedFast: "Rask", speedTurbo: "Turbo",
comparisons: "Sammenligninger", writes: "Skrivinger", step: "Steg", of: "av", sorted: "Sortert",
license: "MIT-lisens", viewLicense: "Lisens", close: "Lukk",
footerRights: "Alle rettigheter forbeholdt.",
growthTitle: "Hvordan arbeidet vokser", growthCaption: "Samme n, svært ulik mengde arbeid.",
narrationTitle: "Hva skjer nå", statsTitle: "Sanntidsstatistikk",
pickAlgorithm: "Velg en algoritme", done: "Ferdig sortert! Hver verdi fant sin plass.",
},
sv: {
brand: "Valgo",
tagline: "Se hur sortering faktiskt fungerar.",
play: "Spela", pause: "Pausa", resume: "Fortsätt", reset: "Blanda igen",
shuffle: "Blanda", algorithm: "Algoritm", language: "Språk",
arraySize: "Array-storlek", elements: "element",
speed: "Hastighet", speedSlow: "Långsam", speedNormal: "Normal", speedFast: "Snabb", speedTurbo: "Turbo",
comparisons: "Jämförelser", writes: "Skrivningar", step: "Steg", of: "av", sorted: "Sorterade",
license: "MIT-licens", viewLicense: "Licens", close: "Stäng",
footerRights: "Alla rättigheter förbehållna.",
growthTitle: "Hur arbetet växer", growthCaption: "Samma n, väldigt olika mängd arbete.",
narrationTitle: "Vad som händer", statsTitle: "Livestatistik",
pickAlgorithm: "Välj en algoritm", done: "Klart! Varje värde hittade sin plats.",
},
ua: {
brand: "Valgo",
tagline: "Подивіться, як насправді працює сортування.",
play: "Старт", pause: "Пауза", resume: "Продовжити", reset: "Перемішати",
shuffle: "Перемішати", algorithm: "Алгоритм", language: "Мова",
arraySize: "Розмір масиву", elements: "елементів",
speed: "Швидкість", speedSlow: "Повільно", speedNormal: "Звичайно", speedFast: "Швидко", speedTurbo: "Турбо",
comparisons: "Порівняння", writes: "Записи", step: "Крок", of: "з", sorted: "Відсортовано",
license: "Ліцензія MIT", viewLicense: "Ліцензія", close: "Закрити",
footerRights: "Усі права захищено.",
growthTitle: "Як зростає обсяг роботи", growthCaption: "Те саме n, зовсім різний обсяг роботи.",
narrationTitle: "Що відбувається", statsTitle: "Статистика наживо",
pickAlgorithm: "Оберіть алгоритм", done: "Відсортовано! Кожне значення знайшло своє місце.",
},
ru: {
brand: "Valgo",
tagline: "Посмотрите, как на самом деле работает сортировка.",
play: "Старт", pause: "Пауза", resume: "Продолжить", reset: "Перемешать",
shuffle: "Перемешать", algorithm: "Алгоритм", language: "Язык",
arraySize: "Размер массива", elements: "элементов",
speed: "Скорость", speedSlow: "Медленно", speedNormal: "Обычно", speedFast: "Быстро", speedTurbo: "Турбо",
comparisons: "Сравнения", writes: "Записи", step: "Шаг", of: "из", sorted: "Отсортировано",
license: "Лицензия MIT", viewLicense: "Лицензия", close: "Закрыть",
footerRights: "Все права защищены.",
growthTitle: "Как растёт объём работы", growthCaption: "Одно и то же n, совершенно разный объём работы.",
narrationTitle: "Что происходит", statsTitle: "Статистика в реальном времени",
pickAlgorithm: "Выберите алгоритм", done: "Отсортировано! Каждое значение нашло своё место.",
},
};
export function t(lang: LanguageCode, key: UIKey): string {
return UI[lang]?.[key] ?? UI.en[key];
}
// ---- Step narration -------------------------------------------------
// One shared vocabulary of "moves" is narrated slightly differently
// depending on which algorithm is doing the moving, so the commentary
// always explains *why*, not just *what*.
type Vars = Record<string, string | number>;
function fmt(template: string, vars: Vars): string {
return template.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
}
const TEMPLATES: Record<LanguageCode, Record<string, string>> = {
en: {
init: "A freshly shuffled array of {n} numbers. Watch {algo} bring order to it.",
compare: "Comparing positions {a} and {b}: {va} vs {vb}.",
swapYes: "{va} > {vb}, so they're out of order — swap positions {a} and {b}.",
overwrite: "Writing {va} into position {a}.",
shift: "Sliding {va} rightward to open a gap.",
selectPivot: "Choosing {va} (position {a}) as the pivot.",
partitionRange: "Partitioning the range [{low}, {high}] around the pivot.",
pivotPlaced: "The pivot is home — {va} is now permanently in its sorted spot at {a}.",
mergeRange: "Merging two sorted halves: [{low}..{mid}] and [{mid1}..{high}].",
mergeWrite: "The smaller candidate, {va}, goes into position {a}.",
markSorted: "Position {a} is locked in as sorted.",
shellGap: "Shrinking the gap to {gap} — comparisons now leap across the array instead of crawling.",
radixDigit: "Now sorting by the digit worth {digit}, least significant first.",
bucketPlace: "{va} drops into bucket {bucket}, grouped by value range.",
bucketSorted: "Sorting the contents of bucket {bucket} on its own.",
countingTally: "Tallying how many times {va} appears.",
done: "Sorted! Every element found its final place.",
},
fr: {
init: "Un tableau de {n} nombres mélangés. Regardez {algo} y mettre de l'ordre.",
compare: "Comparaison des positions {a} et {b} : {va} contre {vb}.",
swapYes: "{va} > {vb}, donc c'est dans le mauvais ordre — échange des positions {a} et {b}.",
overwrite: "Écriture de {va} en position {a}.",
shift: "Décalage de {va} vers la droite pour laisser de la place.",
selectPivot: "Choix de {va} (position {a}) comme pivot.",
partitionRange: "Partition de la plage [{low}, {high}] autour du pivot.",
pivotPlaced: "Le pivot est à sa place — {va} est désormais définitivement trié en position {a}.",
mergeRange: "Fusion de deux moitiés déjà triées : [{low}..{mid}] et [{mid1}..{high}].",
mergeWrite: "Le plus petit candidat, {va}, est placé en position {a}.",
markSorted: "La position {a} est désormais fixée comme triée.",
shellGap: "Réduction de l'écart à {gap} — les comparaisons sautent maintenant à travers le tableau.",
radixDigit: "Tri maintenant selon le chiffre de rang {digit}, du moins significatif d'abord.",
bucketPlace: "{va} est placé dans le compartiment {bucket}, selon sa valeur.",
bucketSorted: "Tri du contenu du compartiment {bucket} indépendamment.",
countingTally: "Comptage du nombre d'occurrences de {va}.",
done: "Trié ! Chaque élément a trouvé sa place finale.",
},
it: {
init: "Un array di {n} numeri appena mescolato. Guarda {algo} metterlo in ordine.",
compare: "Confronto tra le posizioni {a} e {b}: {va} contro {vb}.",
swapYes: "{va} > {vb}, quindi sono nell'ordine sbagliato — scambio le posizioni {a} e {b}.",
overwrite: "Scrivo {va} nella posizione {a}.",
shift: "Sposto {va} verso destra per fare spazio.",
selectPivot: "Scelgo {va} (posizione {a}) come pivot.",
partitionRange: "Partiziono l'intervallo [{low}, {high}] attorno al pivot.",
pivotPlaced: "Il pivot è a casa — {va} è ora definitivamente ordinato in posizione {a}.",
mergeRange: "Fondo due metà già ordinate: [{low}..{mid}] e [{mid1}..{high}].",
mergeWrite: "Il candidato più piccolo, {va}, va in posizione {a}.",
markSorted: "La posizione {a} è ora fissata come ordinata.",
shellGap: "Riduco il gap a {gap} — i confronti ora saltano attraverso l'array invece di procedere passo passo.",
radixDigit: "Ora ordino in base alla cifra di peso {digit}, a partire dalla meno significativa.",
bucketPlace: "{va} finisce nel bucket {bucket}, in base al suo valore.",
bucketSorted: "Ordino il contenuto del bucket {bucket} per conto suo.",
countingTally: "Conto quante volte compare {va}.",
done: "Ordinato! Ogni elemento ha trovato il suo posto finale.",
},
de: {
init: "Ein frisch gemischtes Array aus {n} Zahlen. Sieh zu, wie {algo} Ordnung schafft.",
compare: "Vergleiche Position {a} und {b}: {va} gegen {vb}.",
swapYes: "{va} > {vb}, also falsche Reihenfolge — tausche Position {a} und {b}.",
overwrite: "Schreibe {va} an Position {a}.",
shift: "Verschiebe {va} nach rechts, um Platz zu schaffen.",
selectPivot: "Wähle {va} (Position {a}) als Pivot.",
partitionRange: "Partitioniere den Bereich [{low}, {high}] um den Pivot herum.",
pivotPlaced: "Der Pivot ist angekommen — {va} steht jetzt endgültig sortiert an Position {a}.",
mergeRange: "Verschmelze zwei bereits sortierte Hälften: [{low}..{mid}] und [{mid1}..{high}].",
mergeWrite: "Der kleinere Kandidat, {va}, kommt an Position {a}.",
markSorted: "Position {a} ist nun endgültig sortiert.",
shellGap: "Verringere den Abstand auf {gap} — Vergleiche springen jetzt über das Array statt zu kriechen.",
radixDigit: "Jetzt wird nach der Ziffernstelle {digit} sortiert, zuerst die niedrigstwertige.",
bucketPlace: "{va} kommt in Eimer {bucket}, gruppiert nach Wertebereich.",
bucketSorted: "Sortiere den Inhalt von Eimer {bucket} für sich.",
countingTally: "Zähle, wie oft {va} vorkommt.",
done: "Fertig sortiert! Jedes Element hat seinen endgültigen Platz gefunden.",
},
es: {
init: "Un array recién mezclado de {n} números. Observa cómo {algo} le pone orden.",
compare: "Comparando las posiciones {a} y {b}: {va} frente a {vb}.",
swapYes: "{va} > {vb}, así que están desordenados — intercambio las posiciones {a} y {b}.",
overwrite: "Escribiendo {va} en la posición {a}.",
shift: "Deslizo {va} hacia la derecha para abrir hueco.",
selectPivot: "Elijo {va} (posición {a}) como pivote.",
partitionRange: "Particiono el rango [{low}, {high}] alrededor del pivote.",
pivotPlaced: "El pivote llegó a casa — {va} ya está definitivamente ordenado en la posición {a}.",
mergeRange: "Fusionando dos mitades ya ordenadas: [{low}..{mid}] y [{mid1}..{high}].",
mergeWrite: "El candidato más pequeño, {va}, va a la posición {a}.",
markSorted: "La posición {a} queda fijada como ordenada.",
shellGap: "Reduzco el intervalo a {gap} — las comparaciones ahora saltan por el array en vez de arrastrarse.",
radixDigit: "Ahora ordenando por el dígito de valor {digit}, empezando por el menos significativo.",
bucketPlace: "{va} cae en el cubo {bucket}, agrupado por rango de valor.",
bucketSorted: "Ordenando el contenido del cubo {bucket} por separado.",
countingTally: "Contando cuántas veces aparece {va}.",
done: "¡Ordenado! Cada elemento encontró su lugar final.",
},
ja: {
init: "{n} 個の数をシャッフルした配列です。{algo} が整えていく様子を見てみましょう。",
compare: "位置 {a} と {b} を比較: {va} と {vb}。",
swapYes: "{va} > {vb} のため順序が逆です — 位置 {a} と {b} を交換します。",
overwrite: "{va} を位置 {a} に書き込みます。",
shift: "{va} を右にずらして隙間を作ります。",
selectPivot: "{va}(位置 {a})をピボットに選びます。",
partitionRange: "ピボットを中心に範囲 [{low}, {high}] を分割します。",
pivotPlaced: "ピボットが定位置に到着 — {va} は位置 {a} で確定的にソート済みです。",
mergeRange: "すでにソート済みの2つの半分 [{low}..{mid}] と [{mid1}..{high}] を結合します。",
mergeWrite: "小さい方の候補 {va} を位置 {a} に書き込みます。",
markSorted: "位置 {a} はソート済みとして確定しました。",
shellGap: "間隔を {gap} に縮めます — 比較が一歩ずつではなく配列を飛び越えて行われます。",
radixDigit: "桁の重み {digit} で並べ替え中です(下位桁から順に)。",
bucketPlace: "{va} は値の範囲に応じてバケット {bucket} に入ります。",
bucketSorted: "バケット {bucket} の中身を個別にソートします。",
countingTally: "{va} が何回出現するかを数えています。",
done: "完了!すべての要素が最終的な位置に収まりました。",
},
no: {
init: "En nyblandet matrise med {n} tall. Se hvordan {algo} skaper orden.",
compare: "Sammenligner posisjon {a} og {b}: {va} mot {vb}.",
swapYes: "{va} > {vb}, altså feil rekkefølge — bytter posisjon {a} og {b}.",
overwrite: "Skriver {va} til posisjon {a}.",
shift: "Flytter {va} mot høyre for å lage plass.",
selectPivot: "Velger {va} (posisjon {a}) som pivot.",
partitionRange: "Partisjonerer området [{low}, {high}] rundt pivoten.",
pivotPlaced: "Pivoten er hjemme — {va} er nå permanent sortert på posisjon {a}.",
mergeRange: "Slår sammen to sorterte halvdeler: [{low}..{mid}] og [{mid1}..{high}].",
mergeWrite: "Den minste kandidaten, {va}, går til posisjon {a}.",
markSorted: "Posisjon {a} er nå låst som sortert.",
shellGap: "Krymper gapet til {gap} — sammenligninger hopper nå over matrisen i stedet for å krype.",
radixDigit: "Sorterer nå etter sifferverdi {digit}, minst betydningsfulle først.",
bucketPlace: "{va} havner i bøtte {bucket}, gruppert etter verdiområde.",
bucketSorted: "Sorterer innholdet i bøtte {bucket} for seg selv.",
countingTally: "Teller hvor mange ganger {va} forekommer.",
done: "Ferdig sortert! Hvert element fant sin endelige plass.",
},
sv: {
init: "En nyblandad array med {n} tal. Se hur {algo} skapar ordning.",
compare: "Jämför position {a} och {b}: {va} mot {vb}.",
swapYes: "{va} > {vb}, så de är i fel ordning — byter position {a} och {b}.",
overwrite: "Skriver {va} till position {a}.",
shift: "Flyttar {va} åt höger för att göra plats.",
selectPivot: "Väljer {va} (position {a}) som pivot.",
partitionRange: "Partitionerar intervallet [{low}, {high}] runt pivoten.",
pivotPlaced: "Pivoten är hemma — {va} är nu permanent sorterad på position {a}.",
mergeRange: "Slår ihop två sorterade halvor: [{low}..{mid}] och [{mid1}..{high}].",
mergeWrite: "Den minsta kandidaten, {va}, hamnar på position {a}.",
markSorted: "Position {a} är nu låst som sorterad.",
shellGap: "Minskar gapet till {gap} — jämförelser hoppar nu över arrayen istället för att krypa.",
radixDigit: "Sorterar nu efter siffervärdet {digit}, minst signifikant först.",
bucketPlace: "{va} hamnar i hink {bucket}, grupperad efter värdeintervall.",
bucketSorted: "Sorterar innehållet i hink {bucket} för sig.",
countingTally: "Räknar hur många gånger {va} förekommer.",
done: "Klart sorterat! Varje element hittade sin slutgiltiga plats.",
},
ua: {
init: "Щойно перемішаний масив із {n} чисел. Подивіться, як {algo} наводить лад.",
compare: "Порівнюємо позиції {a} і {b}: {va} проти {vb}.",
swapYes: "{va} > {vb}, тож порядок неправильний — міняємо місцями позиції {a} і {b}.",
overwrite: "Записуємо {va} у позицію {a}.",
shift: "Зсуваємо {va} праворуч, щоб звільнити місце.",
selectPivot: "Обираємо {va} (позиція {a}) як опорний елемент.",
partitionRange: "Розбиваємо діапазон [{low}, {high}] навколо опорного елемента.",
pivotPlaced: "Опорний елемент на місці — {va} тепер остаточно відсортовано на позиції {a}.",
mergeRange: "Зливаємо дві вже відсортовані половини: [{low}..{mid}] і [{mid1}..{high}].",
mergeWrite: "Менший кандидат, {va}, іде на позицію {a}.",
markSorted: "Позиція {a} тепер зафіксована як відсортована.",
shellGap: "Зменшуємо проміжок до {gap} — порівняння тепер перестрибують масив, а не повзуть.",
radixDigit: "Тепер сортуємо за розрядом {digit}, спочатку наймолодшим.",
bucketPlace: "{va} потрапляє в кошик {bucket} за діапазоном значення.",
bucketSorted: "Сортуємо вміст кошика {bucket} окремо.",
countingTally: "Рахуємо, скільки разів трапляється {va}.",
done: "Відсортовано! Кожен елемент знайшов своє остаточне місце.",
},
ru: {
init: "Только что перемешанный массив из {n} чисел. Смотрите, как {algo} наводит порядок.",
compare: "Сравниваем позиции {a} и {b}: {va} против {vb}.",
swapYes: "{va} > {vb}, значит порядок неверный — меняем местами позиции {a} и {b}.",
overwrite: "Записываем {va} в позицию {a}.",
shift: "Сдвигаем {va} вправо, чтобы освободить место.",
selectPivot: "Выбираем {va} (позиция {a}) в качестве опорного элемента.",
partitionRange: "Разбиваем диапазон [{low}, {high}] вокруг опорного элемента.",
pivotPlaced: "Опорный элемент на месте — {va} теперь окончательно отсортирован на позиции {a}.",
mergeRange: "Сливаем две уже отсортированные половины: [{low}..{mid}] и [{mid1}..{high}].",
mergeWrite: "Меньший кандидат, {va}, идёт на позицию {a}.",
markSorted: "Позиция {a} теперь зафиксирована как отсортированная.",
shellGap: "Уменьшаем промежуток до {gap} — сравнения теперь перепрыгивают через массив, а не ползут.",
radixDigit: "Теперь сортируем по разряду {digit}, начиная с младшего.",
bucketPlace: "{va} попадает в корзину {bucket} по диапазону значения.",
bucketSorted: "Сортируем содержимое корзины {bucket} отдельно.",
countingTally: "Считаем, сколько раз встречается {va}.",
done: "Отсортировано! Каждый элемент нашёл своё окончательное место.",
},
};
const algoNameKey: Record<AlgorithmKey, UIKey | null> = {
bubble: null, insertion: null, heap: null, quick: null, merge: null,
radix: null, counting: null, bucket: null, shell: null,
};
export function narrate(step: Step, algo: AlgorithmKey, algoTitle: string, lang: LanguageCode): string {
const templates = TEMPLATES[lang] ?? TEMPLATES.en;
const arr = step.array;
const [a, b] = step.indices;
const va = a !== undefined ? arr[a] : undefined;
const vb = b !== undefined ? arr[b] : undefined;
const vars: Vars = {
n: arr.length, algo: algoTitle,
a, b, va: va ?? "", vb: vb ?? "",
low: step.range?.[0] ?? "", high: step.range?.[1] ?? "",
mid: step.meta?.mid ?? "", mid1: (step.meta?.mid ?? 0) + 1,
gap: step.meta?.gap ?? "", digit: step.meta?.digit ?? "",
bucket: step.meta?.bucket ?? "",
};
switch (step.action as ActionType) {
case "init":
return fmt(templates.init, vars);
case "compare":
return fmt(templates.compare, vars);
case "swap":
return fmt(templates.swapYes, vars);
case "overwrite":
return fmt(templates.overwrite, vars);
case "shift":
return fmt(templates.shift, vars);
case "select-pivot":
return fmt(templates.selectPivot, vars);
case "partition-range":
return fmt(templates.partitionRange, vars);
case "pivot-placed":
return fmt(templates.pivotPlaced, vars);
case "merge-range":
return fmt(templates.mergeRange, vars);
case "merge-write":
return fmt(templates.mergeWrite, vars);
case "mark-sorted":
return fmt(templates.markSorted, vars);
case "gap-set":
return algo === "radix" ? fmt(templates.radixDigit, vars) : fmt(templates.shellGap, vars);
case "count":
return fmt(templates.countingTally, vars);
case "bucket-place":
return fmt(templates.bucketPlace, vars);
case "bucket-sorted":
return fmt(templates.bucketSorted, vars);
case "done":
return templates.done;
default:
return "";
}
}