{{ progressText }}
👁️
@@ -83,4 +85,4 @@ const progressText = computed(() => `${store.progressPercentage.toFixed(3)}%`);
opacity: 0.7;
cursor: pointer;
}
-
\ No newline at end of file
+
diff --git a/src/components/WinModal.vue b/src/components/WinModal.vue
index b11877a..51cf663 100644
--- a/src/components/WinModal.vue
+++ b/src/components/WinModal.vue
@@ -2,8 +2,10 @@
import { onMounted, onUnmounted, ref } from 'vue';
import { Fireworks } from 'fireworks-js';
import { usePuzzleStore } from '@/stores/puzzle';
+import { useI18n } from '@/composables/useI18n';
const store = usePuzzleStore();
+const { t } = useI18n();
const fireworksRef = ref(null);
let fireworksInstance = null;
let audioContext = null;
@@ -110,18 +112,18 @@ onUnmounted(() => {
-
GRATULACJE!
-
Rozwiązałeś zagadkę!
+
{{ t('win.title') }}
+
{{ t('win.message') }}
- Czas:
+ {{ t('win.time') }}
{{ store.elapsedTime }}s
-
+
diff --git a/src/composables/useI18n.js b/src/composables/useI18n.js
new file mode 100644
index 0000000..0a68a3f
--- /dev/null
+++ b/src/composables/useI18n.js
@@ -0,0 +1,113 @@
+import { ref, computed } from 'vue';
+
+const detectLocale = () => {
+ if (typeof navigator === 'undefined') return 'en';
+ const browserLocale = (navigator.languages && navigator.languages[0]) || navigator.language || 'en';
+ const short = browserLocale.toLowerCase().split('-')[0];
+ return short === 'pl' ? 'pl' : 'en';
+};
+
+const messages = {
+ pl: {
+ 'app.title': 'Nonograms',
+ 'level.easy': 'ŁATWY 5X5',
+ 'level.medium': 'ŚREDNI 10X10',
+ 'level.hard': 'TRUDNY 15X15',
+ 'level.custom': 'WŁASNY',
+ 'level.guide': 'PODPOWIEDŹ ❓',
+ 'actions.reset': 'RESET',
+ 'actions.random': 'NOWA LOSOWA',
+ 'actions.undo': 'COFNIJ',
+ 'status.time': 'CZAS',
+ 'status.moves': 'RUCHY',
+ 'status.progress': 'POSTĘP',
+ 'fixed.time': 'Czas:',
+ 'fixed.progress': 'Postęp:',
+ 'fixed.hide': 'Ukryj',
+ 'fixed.show': 'Pokaż',
+ 'guide.play': 'START',
+ 'guide.pause': 'PAUZA',
+ 'guide.step': 'KROK',
+ 'guide.speed': 'SZYBKOŚĆ',
+ 'guide.waiting': 'Oczekiwanie...',
+ 'guide.solved': 'Rozwiązane!',
+ 'custom.title': 'GRA WŁASNA',
+ 'custom.prompt': 'Wprowadź rozmiar siatki (5 - 100):',
+ 'custom.cancel': 'Anuluj',
+ 'custom.start': 'Start',
+ 'custom.sizeError': 'Rozmiar musi być między 5 a 100!',
+ 'win.title': 'GRATULACJE!',
+ 'win.message': 'Rozwiązałeś zagadkę!',
+ 'win.time': 'Czas:',
+ 'win.playAgain': 'Zagraj Ponownie'
+ },
+ en: {
+ 'app.title': 'Nonograms',
+ 'level.easy': 'EASY 5X5',
+ 'level.medium': 'MEDIUM 10X10',
+ 'level.hard': 'HARD 15X15',
+ 'level.custom': 'CUSTOM',
+ 'level.guide': 'GUIDE ❓',
+ 'actions.reset': 'RESET',
+ 'actions.random': 'NEW RANDOM',
+ 'actions.undo': 'UNDO',
+ 'status.time': 'TIME',
+ 'status.moves': 'MOVES',
+ 'status.progress': 'PROGRESS',
+ 'fixed.time': 'Time:',
+ 'fixed.progress': 'Progress:',
+ 'fixed.hide': 'Hide',
+ 'fixed.show': 'Show',
+ 'guide.play': 'PLAY',
+ 'guide.pause': 'PAUSE',
+ 'guide.step': 'STEP',
+ 'guide.speed': 'SPEED',
+ 'guide.waiting': 'Waiting...',
+ 'guide.solved': 'Solved!',
+ 'custom.title': 'CUSTOM GAME',
+ 'custom.prompt': 'Enter grid size (5 - 100):',
+ 'custom.cancel': 'Cancel',
+ 'custom.start': 'Start',
+ 'custom.sizeError': 'Size must be between 5 and 100!',
+ 'win.title': 'CONGRATULATIONS!',
+ 'win.message': 'You solved the puzzle!',
+ 'win.time': 'Time:',
+ 'win.playAgain': 'Play Again'
+ }
+};
+
+const locale = ref(detectLocale());
+
+const format = (text, params = {}) => {
+ return text.replace(/\{(\w+)\}/g, (_, key) => {
+ const value = params[key];
+ return value === undefined ? `{${key}}` : String(value);
+ });
+};
+
+const t = (key, params) => {
+ const lang = messages[locale.value] || messages.en;
+ const value = lang[key] || messages.en[key] || key;
+ return typeof value === 'string' ? format(value, params) : key;
+};
+
+const setLocale = (value) => {
+ locale.value = messages[value] ? value : 'en';
+ if (typeof document !== 'undefined') {
+ document.documentElement.lang = locale.value;
+ document.title = t('app.title');
+ }
+};
+
+if (typeof document !== 'undefined') {
+ document.documentElement.lang = locale.value;
+ document.title = messages[locale.value]?.['app.title'] || 'Nonograms';
+}
+
+export function useI18n() {
+ return {
+ locale: computed(() => locale.value),
+ t,
+ setLocale
+ };
+}
diff --git a/src/composables/useSolver.js b/src/composables/useSolver.js
index e2406d8..dd17c89 100644
--- a/src/composables/useSolver.js
+++ b/src/composables/useSolver.js
@@ -1,15 +1,17 @@
import { ref, computed, onUnmounted } from 'vue';
import { usePuzzleStore } from '@/stores/puzzle';
+import { useI18n } from '@/composables/useI18n';
export function useSolver() {
const store = usePuzzleStore();
+ const { t, locale } = useI18n();
const isPlaying = ref(false);
const isProcessing = ref(false);
const speedIndex = ref(0);
const speeds = [1000, 500, 250, 125];
const speedLabels = ['x1', 'x2', 'x3', 'x4'];
- const statusText = ref('Oczekiwanie...');
+ const statusText = ref(t('guide.waiting'));
let intervalId = null;
let worker = null;
@@ -18,7 +20,7 @@ export function useSolver() {
function step() {
if (store.isGameWon) {
pause();
- statusText.value = "Rozwiązane!";
+ statusText.value = t('guide.solved');
return;
}
if (isProcessing.value) return;
@@ -28,7 +30,7 @@ export function useSolver() {
const playerGrid = store.playerGrid.map(row => row.slice());
const solution = store.solution.map(row => row.slice());
const id = ++requestId;
- worker.postMessage({ id, playerGrid, solution });
+ worker.postMessage({ id, playerGrid, solution, locale: locale.value });
}
function togglePlay() {
diff --git a/src/workers/solverWorker.js b/src/workers/solverWorker.js
index 2816646..d31b85f 100644
--- a/src/workers/solverWorker.js
+++ b/src/workers/solverWorker.js
@@ -1,5 +1,45 @@
import { calculateHints } from '../utils/puzzleUtils.js';
+const messages = {
+ pl: {
+ 'worker.solved': 'Rozwiązane!',
+ 'worker.logicRow': 'Logika: Wiersz {row}, Kolumna {col} -> {state}',
+ 'worker.logicCol': 'Logika: Kolumna {col}, Wiersz {row} -> {state}',
+ 'worker.guess': 'Zgadywanie: Wiersz {row}, Kolumna {col}',
+ 'worker.done': 'Koniec!',
+ 'worker.state.filled': 'Pełne',
+ 'worker.state.empty': 'Puste'
+ },
+ en: {
+ 'worker.solved': 'Solved!',
+ 'worker.logicRow': 'Logic: Row {row}, Column {col} -> {state}',
+ 'worker.logicCol': 'Logic: Column {col}, Row {row} -> {state}',
+ 'worker.guess': 'Guessing: Row {row}, Column {col}',
+ 'worker.done': 'Done!',
+ 'worker.state.filled': 'Filled',
+ 'worker.state.empty': 'Empty'
+ }
+};
+
+const resolveLocale = (value) => {
+ if (!value) return 'en';
+ const short = String(value).toLowerCase().split('-')[0];
+ return short === 'pl' ? 'pl' : 'en';
+};
+
+const format = (text, params = {}) => {
+ return text.replace(/\{(\w+)\}/g, (_, key) => {
+ const value = params[key];
+ return value === undefined ? `{${key}}` : String(value);
+ });
+};
+
+const t = (locale, key, params) => {
+ const lang = messages[locale] || messages.en;
+ const value = lang[key] || messages.en[key] || key;
+ return typeof value === 'string' ? format(value, params) : key;
+};
+
const getPermutations = (length, hints) => {
const results = [];
const recurse = (index, hintIndex, currentLine) => {
@@ -73,9 +113,9 @@ const isSolved = (grid, solution) => {
return true;
};
-const handleStep = (playerGrid, solution) => {
+const handleStep = (playerGrid, solution, locale) => {
if (isSolved(playerGrid, solution)) {
- return { type: 'done', statusText: 'Rozwiązane!' };
+ return { type: 'done', statusText: t(locale, 'worker.solved') };
}
const size = solution.length;
@@ -86,12 +126,13 @@ const handleStep = (playerGrid, solution) => {
const hints = rowHints[r];
const result = solveLineLogic(rowLine, hints, size);
if (result.index !== -1) {
+ const stateLabel = t(locale, result.state === 1 ? 'worker.state.filled' : 'worker.state.empty');
return {
type: 'move',
r,
c: result.index,
state: result.state,
- statusText: `Logika: Wiersz ${r + 1}, Kolumna ${result.index + 1} -> ${result.state === 1 ? 'Pełne' : 'Puste'}`
+ statusText: t(locale, 'worker.logicRow', { row: r + 1, col: result.index + 1, state: stateLabel })
};
}
}
@@ -102,12 +143,13 @@ const handleStep = (playerGrid, solution) => {
const hints = colHints[c];
const result = solveLineLogic(colLine, hints, size);
if (result.index !== -1) {
+ const stateLabel = t(locale, result.state === 1 ? 'worker.state.filled' : 'worker.state.empty');
return {
type: 'move',
r: result.index,
c,
state: result.state,
- statusText: `Logika: Kolumna ${c + 1}, Wiersz ${result.index + 1} -> ${result.state === 1 ? 'Pełne' : 'Puste'}`
+ statusText: t(locale, 'worker.logicCol', { row: result.index + 1, col: c + 1, state: stateLabel })
};
}
}
@@ -128,17 +170,18 @@ const handleStep = (playerGrid, solution) => {
r,
c,
state: newState,
- statusText: `Zgadywanie: Wiersz ${r + 1}, Kolumna ${c + 1}`
+ statusText: t(locale, 'worker.guess', { row: r + 1, col: c + 1 })
};
}
}
}
- return { type: 'done', statusText: 'Koniec!' };
+ return { type: 'done', statusText: t(locale, 'worker.done') };
};
self.onmessage = (event) => {
- const { id, playerGrid, solution } = event.data;
- const result = handleStep(playerGrid, solution);
+ const { id, playerGrid, solution, locale } = event.data;
+ const resolved = resolveLocale(locale);
+ const result = handleStep(playerGrid, solution, resolved);
self.postMessage({ id, ...result });
};