From 8ac1d3d6d803d8b67766a19fe889ea54cbe92b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87A=C4=9EAN=20TURGUT?= Date: Sat, 17 Oct 2026 11:40:16 +0300 Subject: [PATCH 1/2] Studio: write new preset defaults in the UI language (#772) The new shader/background name, the default slider label, variation names and the starter shader comment header become the user's own editable text, so they are translated at creation like the #759 import fix. Adds EN entries for the header lines and a test. Test count 3186. Co-authored-by: caganturgut <271973834+caganturgut@users.noreply.github.com> --- CHANGELOG.md | 5 ++ README.md | 4 +- README.tr.md | 4 +- ROADMAP.md | 2 +- src/admin/studio.js | 35 +++++++------ src/shared/i18n.js | 8 +++ tests/studio-new-preset-i18n.test.js | 73 ++++++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 tests/studio-new-preset-i18n.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ebf88..25260ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,11 @@ GitHub. The full reasoning and the measurements behind each change are in [ROADM ### Studio +- **New presets start in the app language [772].** In English, Studio → "+ Shader" opened a visualizer + named "Yeni Görselleştirici", and the discard-changes prompt showed that Turkish name. The name, + the default "Amount" slider label, the variation names and the starter shader's comment header + become the user's own editable text, so they are now written in the UI language at creation, + like the import fix in [759]. - **Imported presets follow the app language [759].** A `.milk`, Shadertoy or ISF file imported through Studio got a Turkish description even in English, and the import notes stayed Turkish. The description is the user's own editable text once saved, so it is now written in the UI diff --git a/README.md b/README.md index 52f9023..7b9cb10 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ layers and **40 GPU effects**, an **OBS overlay**, **Spout / Syphon**, **project [![Downloads](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=downloads)](https://github.com/CaYatur/SoundVisualizer/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-e11d2a.svg)](LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#download) -[![Tests](https://img.shields.io/badge/tests-3183%20passing-2ea043.svg)](#tests) +[![Tests](https://img.shields.io/badge/tests-3186%20passing-2ea043.svg)](#tests) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) Download for Windows @@ -1741,7 +1741,7 @@ npm test npm start -- --smoke ``` -**3183 unit tests, all passing.** They are written to check answers, not to exercise lines: +**3186 unit tests, all passing.** They are written to check answers, not to exercise lines: - **Formulas** are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor staying diff --git a/README.tr.md b/README.tr.md index 8384031..fbd1e0b 100644 --- a/README.tr.md +++ b/README.tr.md @@ -18,7 +18,7 @@ destesi**, ve **MCP üzerinden yapay zekâ kontrolü**. [![İndirme](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=indirme)](https://github.com/CaYatur/SoundVisualizer/releases) [![Lisans: MIT](https://img.shields.io/badge/Lisans-MIT-e11d2a.svg)](LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#hızlı-başlangıç) -[![Test](https://img.shields.io/badge/test-3183%20geçiyor-2ea043.svg)](#testler) +[![Test](https://img.shields.io/badge/test-3186%20geçiyor-2ea043.svg)](#testler) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) Windows için indir @@ -1744,7 +1744,7 @@ npm test npm start -- --smoke ``` -**3183 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: +**3186 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: - **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre üzerinde kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı kalması ve görüş diff --git a/ROADMAP.md b/ROADMAP.md index b8c2b92..aad1a43 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -131,7 +131,7 @@ npm test npm start -- --smoke ``` -- **3183 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; +- **3186 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; 105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at that tag — 469 more with v3.1.4, most of them from the MilkDrop work — 1597 at that tag — 1402 more with v3.1.5 — 2999 at that tag — and 27 diff --git a/src/admin/studio.js b/src/admin/studio.js index dacfeb7..6bdd480 100644 --- a/src/admin/studio.js +++ b/src/admin/studio.js @@ -32,16 +32,20 @@ let compileState = { ok: true, message: '', line: 0 }; let compileTimer = null; - const STARTER_SHADER = `// Kendi görselleştiricin. -// Kullanabileceklerin: -// sv_resolution, sv_time, sv_level, sv_bass, sv_mid, sv_treble, sv_beat -// sv_spec(x) -> 0..1 konumundaki spektrum değeri -// sv_waveAt(x) -> dalga formu (-1..1) -// sv_col(x) -> senin 5 renkli paletinden renk -// sv_prev -> bir önceki kare (geri besleme), sv_media -> kamera/video -// Aşağıya kendi kaydırıcılarını da ekleyebilirsin (Parametreler bölümü). - -void mainImage(out vec4 fragColor, in vec2 fragCoord) { + /* Şablonun başlık yorumu kullanıcının düzenleyeceği koda giriyor; içe aktarılan + açıklamalar gibi (#759) oluşturulurken arayüz dilinde yazılıyor. Satırlar + ayrı çevriliyor: birleşik metin sözlükte eşleşmez. */ + const STARTER_HEADER = [ + '// Kendi görselleştiricin.', + '// Kullanabileceklerin:', + '// sv_resolution, sv_time, sv_level, sv_bass, sv_mid, sv_treble, sv_beat', + '// sv_spec(x) -> 0..1 konumundaki spektrum değeri', + '// sv_waveAt(x) -> dalga formu (-1..1)', + '// sv_col(x) -> senin 5 renkli paletinden renk', + '// sv_prev -> bir önceki kare (geri besleme), sv_media -> kamera/video', + '// Aşağıya kendi kaydırıcılarını da ekleyebilirsin (Parametreler bölümü).', + ]; + const STARTER_BODY = `void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = (fragCoord - 0.5 * sv_resolution) / min(sv_resolution.x, sv_resolution.y); float r = length(uv); float a = atan(uv.y, uv.x); @@ -54,6 +58,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { fragColor = vec4(col, clamp(ring + sv_bass * 0.2, 0.0, 1.0)); }`; + const starterShader = () => STARTER_HEADER.map(trStudio).join('\n') + '\n\n' + STARTER_BODY; // ========================================================================== // GLSL renklendirme @@ -224,12 +229,14 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { // ========================================================================== function newShader(kind) { return IS().normalize({ - name: kind === 'background' ? 'Yeni Arkaplan' : 'Yeni Görselleştirici', + /* Ad ve etiket kullanıcının kendi düzenlenebilir metni oluyor; editör onu + çevirmiyor. Bu yüzden oluşturulurken arayüz dilinde yazılıyor (#759 gibi). */ + name: trStudio(kind === 'background' ? 'Yeni Arkaplan' : 'Yeni Görselleştirici'), kind, engine: 'shader', - shader: STARTER_SHADER, + shader: starterShader(), controls: [ - { name: 'uAmount', label: 'Miktar', type: 'slider', min: 0, max: 2, step: 0.01, default: 1 }, + { name: 'uAmount', label: trStudio('Miktar'), type: 'slider', min: 0, max: 2, step: 0.01, default: 1 }, ], }); } @@ -250,7 +257,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { function newVariation(kind) { const look = currentLook(P().cfg(), kind); return IS().normalize({ - name: kind === 'background' ? 'Arkaplan Varyasyonum' : 'Görselleştiricim', + name: trStudio(kind === 'background' ? 'Arkaplan Varyasyonum' : 'Görselleştiricim'), kind, engine: 'variation', base: kind === 'background' ? look.background.type : look.visualizer.type, diff --git a/src/shared/i18n.js b/src/shared/i18n.js index 1fa5167..acc5f9e 100644 --- a/src/shared/i18n.js +++ b/src/shared/i18n.js @@ -2870,6 +2870,14 @@ 'Yeni Görselleştirici': 'New Visualizer', 'Arkaplan Varyasyonum': 'My Background Variation', 'Görselleştiricim': 'My Visualizer', + // Yeni shader şablonunun başlık yorumu (satır satır) + '// Kendi görselleştiricin.': '// Your own visualizer.', + '// Kullanabileceklerin:': '// What you can use:', + '// sv_spec(x) -> 0..1 konumundaki spektrum değeri': '// sv_spec(x) -> spectrum value at position 0..1', + '// sv_waveAt(x) -> dalga formu (-1..1)': '// sv_waveAt(x) -> waveform (-1..1)', + '// sv_col(x) -> senin 5 renkli paletinden renk': '// sv_col(x) -> a color from your 5-color palette', + '// sv_prev -> bir önceki kare (geri besleme), sv_media -> kamera/video': '// sv_prev -> the previous frame (feedback), sv_media -> camera/video', + '// Aşağıya kendi kaydırıcılarını da ekleyebilirsin (Parametreler bölümü).': '// You can add your own sliders below too (Parameters section).', 'Dışa aktarılacak preset yok.': 'No presets to export.', 'CAYADEV Preset Paketi': 'CAYADEV Preset Pack', 'Dosyaya yazıldı.': 'Written to file.', diff --git a/tests/studio-new-preset-i18n.test.js b/tests/studio-new-preset-i18n.test.js new file mode 100644 index 0000000..a308c55 --- /dev/null +++ b/tests/studio-new-preset-i18n.test.js @@ -0,0 +1,73 @@ +'use strict'; +/* STUDIO'DA YENİ PRESET VARSAYILANLARI ARAYÜZ DİLİNDE. + * + * İngilizce arayüzde "+ Shader" yeni görselleştiriciyi "Yeni Görselleştirici" + * adıyla açıyordu; değişiklikleri at onayı da bu adı gösteriyordu. Ad, etiket + * ve şablonun başlık yorumu kullanıcının kendi düzenlenebilir metni oluyor + * ve editör onu çevirmiyor; bu yüzden oluşturulurken çevriliyorlar (#759 gibi). + */ +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const read = (p) => fs.readFileSync(path.join(__dirname, '..', p), 'utf8'); +const norm = (v) => String(v).replace(/\s+/g, ' ').trim(); + +function dictionary() { + const src = read('src/shared/i18n.js'); + const body = src.slice(src.indexOf('const EN = {'), src.indexOf('const EN_NORMALIZED')); + const STR = "'((?:[^'\\\\]|\\\\.)*)'"; + const re = new RegExp(STR + '\\s*:\\s*' + STR, 'g'); + const unesc = (s) => s.replace(/\\(.)/g, '$1'); + const map = new Map(); + let m; + while ((m = re.exec(body))) map.set(norm(unesc(m[1])), unesc(m[2])); + return map; +} + +const studio = read('src/admin/studio.js'); +const fnBody = (name) => { + const i = studio.indexOf('function ' + name + '('); + assert.ok(i >= 0, name + ' bulunamadı'); + return studio.slice(i, studio.indexOf('\n }\n', i)); +}; + +test('yeni shader ve varyasyon adları ile etiketi çevrilerek yazılıyor', () => { + const d = dictionary(); + const shader = fnBody('newShader'); + const variation = fnBody('newVariation'); + assert.match(shader, /name: trStudio\(kind === 'background' \? 'Yeni Arkaplan' : 'Yeni Görselleştirici'\)/); + assert.match(shader, /label: trStudio\('Miktar'\)/); + assert.match(shader, /shader: starterShader\(\)/); + assert.match(variation, /name: trStudio\(kind === 'background' \? 'Arkaplan Varyasyonum' : 'Görselleştiricim'\)/); + for (const k of ['Yeni Arkaplan', 'Yeni Görselleştirici', 'Miktar', 'Arkaplan Varyasyonum', 'Görselleştiricim']) { + assert.ok(d.has(k), 'çevirisi yok: ' + k); + } +}); + +test('şablon başlık yorumunun Türkçe her satırının İngilizcesi var', () => { + const d = dictionary(); + const m = /const STARTER_HEADER = \[([\s\S]*?)\];/.exec(studio); + assert.ok(m, 'STARTER_HEADER bulunamadı'); + const lines = [...m[1].matchAll(/'((?:[^'\\]|\\.)*)'/g)].map((x) => x[1]); + assert.ok(lines.length >= 6); + for (const l of lines) { + assert.ok(l.startsWith('//'), 'yorum değil: ' + l); + if (!/[^\x00-\x7f]|[a-z]+ [a-z]+ [a-z]/i.test(l.replace(/sv_\w+(\(x\))?,?/g, ''))) continue; + const en = d.get(norm(l)); + assert.ok(en, 'çevirisi yok: ' + l); + assert.ok(en.startsWith('//'), 'çeviri yorum olarak kalmalı: ' + en); + // Hizalı kolonlar korunsun: "->" aynı sütunda + if (l.includes('->')) assert.strictEqual(en.indexOf('->'), l.indexOf('->'), en); + } + assert.ok(!/STARTER_SHADER/.test(studio), 'eski sabit şablon kalmamalı'); +}); + +test('i18n sözlüğünde yinelenen anahtar yok (yeni satırlar)', () => { + const src = read('src/shared/i18n.js'); + for (const k of ['// Kendi görselleştiricin.', '// Kullanabileceklerin:', 'Yeni Görselleştirici']) { + const n = src.split("'" + k + "':").length - 1; + assert.strictEqual(n, 1, k + ' ' + n + ' kez'); + } +}); From 79e71cd953f68d973d048d2720d2a24cf286c68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87A=C4=9EAN=20TURGUT?= Date: Sat, 17 Oct 2026 11:53:00 +0300 Subject: [PATCH 2/2] Studio: no discard prompt for an untouched new draft; self-test skips user content (#772) startDraft marked every new draft dirty, so discarding an unedited one asked for confirmation in both languages (pre-existing on main). The Save button stays highlighted for unsaved drafts via !selectedId. The self-test untranslated scan now skips elements marked data-user-text (text subtree) or data-user-attr (title/placeholder); Studio marks user preset names with them. Test count 3189. Co-authored-by: caganturgut <271973834+caganturgut@users.noreply.github.com> --- CHANGELOG.md | 3 +++ README.md | 4 ++-- README.tr.md | 4 ++-- ROADMAP.md | 2 +- src/admin/studio.js | 11 ++++++++--- src/main/main.js | 5 +++++ tests/studio-new-preset-i18n.test.js | 29 +++++++++++++++++++++++++++- 7 files changed, 49 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25260ff..a6555da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,9 @@ GitHub. The full reasoning and the measurements behind each change are in [ROADM the default "Amount" slider label, the variation names and the starter shader's comment header become the user's own editable text, so they are now written in the UI language at creation, like the import fix in [759]. + A new draft that was not edited no longer asks before it is discarded (it did in both languages + before), and the self-test's untranslated-text scan skips user content marked with + `data-user-text` / `data-user-attr`, so saved preset names are no longer flagged. - **Imported presets follow the app language [759].** A `.milk`, Shadertoy or ISF file imported through Studio got a Turkish description even in English, and the import notes stayed Turkish. The description is the user's own editable text once saved, so it is now written in the UI diff --git a/README.md b/README.md index 7b9cb10..43e53ff 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ layers and **40 GPU effects**, an **OBS overlay**, **Spout / Syphon**, **project [![Downloads](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=downloads)](https://github.com/CaYatur/SoundVisualizer/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-e11d2a.svg)](LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#download) -[![Tests](https://img.shields.io/badge/tests-3186%20passing-2ea043.svg)](#tests) +[![Tests](https://img.shields.io/badge/tests-3189%20passing-2ea043.svg)](#tests) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) Download for Windows @@ -1741,7 +1741,7 @@ npm test npm start -- --smoke ``` -**3186 unit tests, all passing.** They are written to check answers, not to exercise lines: +**3189 unit tests, all passing.** They are written to check answers, not to exercise lines: - **Formulas** are checked against values derived by hand from their definitions — Viviani's curve staying on its sphere, the torus tube radius, Chladni's m↔n antisymmetry, every attractor staying diff --git a/README.tr.md b/README.tr.md index fbd1e0b..945fb0b 100644 --- a/README.tr.md +++ b/README.tr.md @@ -18,7 +18,7 @@ destesi**, ve **MCP üzerinden yapay zekâ kontrolü**. [![İndirme](https://img.shields.io/github/downloads/CaYatur/SoundVisualizer/total?label=indirme)](https://github.com/CaYatur/SoundVisualizer/releases) [![Lisans: MIT](https://img.shields.io/badge/Lisans-MIT-e11d2a.svg)](LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-111997.svg)](#hızlı-başlangıç) -[![Test](https://img.shields.io/badge/test-3186%20geçiyor-2ea043.svg)](#testler) +[![Test](https://img.shields.io/badge/test-3189%20geçiyor-2ea043.svg)](#testler) [![Electron](https://img.shields.io/badge/Electron-43-47848F.svg)](https://www.electronjs.org/) Windows için indir @@ -1744,7 +1744,7 @@ npm test npm start -- --smoke ``` -**3186 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: +**3189 birim testi, hepsi geçiyor.** Satır çalıştırmak için değil, cevap denetlemek için yazıldılar: - **Formüller**, tanımlarından elle türetilmiş değerlerle sınanıyor — Viviani eğrisinin küre üzerinde kalması, simidin boru yarıçapı, Chladni'nin m↔n antisimetrisi, her çekicinin sınırlı kalması ve görüş diff --git a/ROADMAP.md b/ROADMAP.md index aad1a43..cf019cc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -131,7 +131,7 @@ npm test npm start -- --smoke ``` -- **3186 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; +- **3189 unit tests, all passing** on `main`. 703 of those shipped in v3.1.0; 105 came with v3.1.1; 163 came with v3.1.2; 157 came with v3.1.3 — 1128 at that tag — 469 more with v3.1.4, most of them from the MilkDrop work — 1597 at that tag — 1402 more with v3.1.5 — 2999 at that tag — and 27 diff --git a/src/admin/studio.js b/src/admin/studio.js index 6bdd480..4c56120 100644 --- a/src/admin/studio.js +++ b/src/admin/studio.js @@ -349,7 +349,10 @@ if (!(await confirmDiscard())) return; draft = make(); selectedId = null; - dirty = true; + /* Dokunulmamış yeni taslak "değişmiş" sayılmaz: atılırken sormaya gerek + yok. Eskiden burada true'ydu ve TR/EN fark etmeden onay çıkıyordu. + Kaydedilmemiş olduğu yine belli: Kaydet düğmesi !selectedId ile vurgulu. */ + dirty = false; compileNow(); P().rerender(); } @@ -559,10 +562,12 @@ class: 'studio-item' + (p.id === selectedId ? ' active' : ''), type: 'button', title: trStudio(p.description || p.name), + // Kendi presetinin adı/açıklaması kullanıcı içeriği: öz test çeviri aramaz + ...(p.builtin ? {} : { 'data-user-attr': '' }), onclick: () => guardedSelect(p.id), }, [ - el('span', { class: 'si-name', text: p.builtin ? trStudio(p.name) : p.name }), + el('span', { class: 'si-name', text: p.builtin ? trStudio(p.name) : p.name, ...(p.builtin ? {} : { 'data-user-text': '' }) }), el('span', { class: 'si-tag', text: trStudio(p.builtin ? 'yerleşik' : p.engine === 'shader' ? 'shader' : 'varyasyon') }), ] ) @@ -767,7 +772,7 @@ } const actions = el('div', { class: 'studio-actions' }, [ - el('button', { id: 'studioSave', class: 'btn ' + (dirty ? 'primary' : ''), type: 'button', icon: 'save', text: 'Kaydet', onclick: saveDraft }), + el('button', { id: 'studioSave', class: 'btn ' + (dirty || !selectedId ? 'primary' : ''), type: 'button', icon: 'save', text: 'Kaydet', onclick: saveDraft }), el('button', { class: 'btn', type: 'button', icon: 'play', text: 'Sahnede Kullan', onclick: applyToScene }), el('button', { class: 'btn ghost small', type: 'button', icon: 'copy', text: 'Çoğalt', diff --git a/src/main/main.js b/src/main/main.js index dc499fd..76e00e0 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -7053,7 +7053,11 @@ async function runSmoke() { // Yer tutucu ve başlık metinleri de taranır: gözle görünür oldukları // halde metin düğümü olmadıkları için kolayca gözden kaçarlar. var attrs = document.querySelectorAll('#sections [placeholder], #sections [title]'); + /* Kullanıcı içeriği (preset, sahne, katman, klip adları) çevrilmez ve + taranmaz: onu çizen öğe data-user-text (metni ve altı) ya da + data-user-attr (yalnız title/placeholder) ile işaretlenir. */ for (var ai = 0; ai < attrs.length; ai++) { + if (attrs[ai].closest('[data-user-attr],[data-user-text]')) continue; var av = (attrs[ai].getAttribute('placeholder') || '') + ' ' + (attrs[ai].getAttribute('title') || ''); if (/[çğıöşüÇĞİÖŞÜ]/.test(av)) out.push(av.trim().slice(0, 90)); } @@ -7067,6 +7071,7 @@ async function runSmoke() { while (el && el.id !== 'sections') { for (var k = 0; k < skip.length; k++) if (el.classList && el.classList.contains(skip[k])) skipIt = true; if (el.tagName === 'OPTION' || el.tagName === 'INPUT') skipIt = true; + if (el.hasAttribute && el.hasAttribute('data-user-text')) skipIt = true; el = el.parentElement; } if (!skipIt) out.push(text.slice(0, 90)); diff --git a/tests/studio-new-preset-i18n.test.js b/tests/studio-new-preset-i18n.test.js index a308c55..c1b68d8 100644 --- a/tests/studio-new-preset-i18n.test.js +++ b/tests/studio-new-preset-i18n.test.js @@ -11,7 +11,7 @@ const assert = require('node:assert'); const fs = require('fs'); const path = require('path'); -const read = (p) => fs.readFileSync(path.join(__dirname, '..', p), 'utf8'); +const read = (p) => fs.readFileSync(path.join(__dirname, '..', p), 'utf8').replace(/\r\n/g, '\n'); const norm = (v) => String(v).replace(/\s+/g, ' ').trim(); function dictionary() { @@ -71,3 +71,30 @@ test('i18n sözlüğünde yinelenen anahtar yok (yeni satırlar)', () => { assert.strictEqual(n, 1, k + ' ' + n + ' kez'); } }); + +/* Öz testin "çevrilmemiş metin" taraması kullanıcının kendi adlarını + (ör. kaydedilmiş "A Yeni Görselleştirici") işaretliyordu. Kullanıcı içeriği + genel bir öznitelikle hariç tutulur; tek tek metin istisnası yok. */ +test('öz test çeviri taraması kullanıcı içeriği özniteliklerini atlıyor', () => { + const main = read('src/main/main.js'); + const i = main.indexOf("untranslated string(s) in English UI"); + const scan = main.slice(main.lastIndexOf('createTreeWalker(document.getElementById', i) - 1500, i); + assert.match(scan, /closest\('\[data-user-attr\],\[data-user-text\]'\)\) continue;/); + assert.match(scan, /hasAttribute\('data-user-text'\)\) skipIt = true;/); + assert.doesNotMatch(scan, /Yeni Görselleştirici/, 'metne özel istisna olmamalı'); +}); + +test('Studio listesi kendi preset adlarını kullanıcı içeriği olarak işaretliyor', () => { + assert.match(studio, /class: 'si-name', text: p\.builtin \? trStudio\(p\.name\) : p\.name, \.\.\.\(p\.builtin \? \{\} : \{ 'data-user-text': '' \}\)/); + assert.match(studio, /\.\.\.\(p\.builtin \? \{\} : \{ 'data-user-attr': '' \}\)/); +}); + +test('dokunulmamış yeni taslak atılırken onay sorulmuyor', () => { + const body = fnBody('startDraft'); + assert.match(body, /dirty = false;/); + assert.doesNotMatch(body, /dirty = true/); + const confirm = fnBody('confirmDiscard'); + assert.match(confirm, /if \(!dirty \|\| !draft\) return true;/); + // Kaydedilmemiş yeni taslakta Kaydet yine vurgulu + assert.match(studio, /class: 'btn ' \+ \(dirty \|\| !selectedId \? 'primary' : ''\)/); +});