|
293 | 293 | state.btnPrev.disabled = state.idx === 0; |
294 | 294 | state.btnNext.textContent = (step.final || state.idx === state.steps.length - 1) ? 'Done' : 'Next'; |
295 | 295 |
|
| 296 | + // Swap in cached translations for this step immediately (no flash of |
| 297 | + // English) when the app UI is being translated. |
| 298 | + try { |
| 299 | + if (window.nym && typeof nym.i18nApplyNow === 'function') nym.i18nApplyNow(state.card); |
| 300 | + } catch (_) { } |
| 301 | + |
296 | 302 | positionStep(); |
297 | 303 | }; |
298 | 304 |
|
|
363 | 369 |
|
364 | 370 | buildSteps(); |
365 | 371 |
|
| 372 | + // If the UI is being translated, pre-translate the whole tutorial script |
| 373 | + // at high priority so each step is ready as the user advances. |
| 374 | + try { |
| 375 | + if (window.nym && typeof nym.i18nPrioritize === 'function' && Array.isArray(state.steps)) { |
| 376 | + const strings = []; |
| 377 | + state.steps.forEach(s => { if (s.title) strings.push(s.title); if (s.body) strings.push(s.body); }); |
| 378 | + nym.i18nPrioritize(strings); |
| 379 | + } |
| 380 | + } catch (_) { } |
| 381 | + |
366 | 382 | state.overlay = document.getElementById('tutorialOverlay'); |
367 | 383 | state.card = document.getElementById('tutorialCard'); |
368 | 384 | state.highlight = document.getElementById('tutorialHighlight'); |
|
437 | 453 | state.started = false; |
438 | 454 | } |
439 | 455 |
|
| 456 | + // All tutorial step strings (title + body), so the i18n layer can |
| 457 | + // pre-translate the whole tour at high priority as soon as a language is |
| 458 | + // chosen — even though the tutorial is lazy-rendered later. |
| 459 | + window.nymTutorialStrings = function () { |
| 460 | + try { |
| 461 | + buildSteps(); |
| 462 | + const out = []; |
| 463 | + (state.steps || []).forEach(s => { if (s.title) out.push(s.title); if (s.body) out.push(s.body); }); |
| 464 | + return out; |
| 465 | + } catch (_) { return []; } |
| 466 | + }; |
| 467 | + |
440 | 468 | // Expose helper to app |
441 | 469 | window.maybeStartTutorial = function (force = false) { |
442 | 470 | try { |
443 | 471 | if (!force) { |
444 | 472 | const seen = localStorage.getItem('nym_tutorial_seen') === 'true'; |
445 | 473 | if (seen) return; |
446 | 474 | } |
447 | | - // Delay a bit to let UI settle after login/restore |
448 | | - setTimeout(() => startTutorial(), 300); |
| 475 | + const startNow = () => setTimeout(() => startTutorial(), 300); |
| 476 | + const nymRef = window.nym; |
| 477 | + const needPicker = !force && nymRef && |
| 478 | + typeof nymRef.showUiLanguagePicker === 'function' && |
| 479 | + typeof nymRef._uiLanguageChosen === 'function' && !nymRef._uiLanguageChosen(); |
| 480 | + if (needPicker) { |
| 481 | + setTimeout(() => { |
| 482 | + nymRef.showUiLanguagePicker({ dismissible: true }) |
| 483 | + .then(() => { nymRef._markUiLanguageChosen(); startNow(); }) |
| 484 | + .catch(() => { nymRef._markUiLanguageChosen(); startNow(); }); |
| 485 | + }, 400); |
| 486 | + return; |
| 487 | + } |
| 488 | + startNow(); |
449 | 489 | } catch (_) { |
450 | 490 | setTimeout(() => startTutorial(), 300); |
451 | 491 | } |
@@ -3261,6 +3301,33 @@ async function showSettings() { |
3261 | 3301 | translateLangSelect.value = nym.settings.translateLanguage || ''; |
3262 | 3302 | } |
3263 | 3303 |
|
| 3304 | + // App-wide UI language selector |
| 3305 | + if (typeof nym.populateUiLanguageSelect === 'function') nym.populateUiLanguageSelect(); |
| 3306 | + const uiLangSelect = document.getElementById('uiLanguageSelect'); |
| 3307 | + if (uiLangSelect) { |
| 3308 | + uiLangSelect.value = nym.getUiLanguage ? nym.getUiLanguage() : ''; |
| 3309 | + uiLangSelect.onchange = function () { |
| 3310 | + const code = uiLangSelect.value || ''; |
| 3311 | + if (typeof nym.applyUiLanguage === 'function') { |
| 3312 | + nym.applyUiLanguage(code).catch(() => { }); |
| 3313 | + } |
| 3314 | + // Adopt the app language as the message translation language too. |
| 3315 | + if (typeof nym._syncTranslateLanguageToUi === 'function') nym._syncTranslateLanguageToUi(code); |
| 3316 | + if (typeof nym._markUiLanguageChosen === 'function') nym._markUiLanguageChosen(); |
| 3317 | + if (typeof nostrSettingsSave === 'function') nostrSettingsSave(); |
| 3318 | + }; |
| 3319 | + } |
| 3320 | + |
| 3321 | + // Auto-translate messages settings |
| 3322 | + if (typeof nym._syncAutoTranslateSettingsUI === 'function') nym._syncAutoTranslateSettingsUI(); |
| 3323 | + const autoTrSelect = document.getElementById('autoTranslateSelect'); |
| 3324 | + if (autoTrSelect) { |
| 3325 | + autoTrSelect.onchange = function () { |
| 3326 | + const sub = document.getElementById('autoTranslateSubOptions'); |
| 3327 | + if (sub) sub.style.display = autoTrSelect.value === 'true' ? '' : 'none'; |
| 3328 | + }; |
| 3329 | + } |
| 3330 | + |
3264 | 3331 | const gesturesEnabledSelect = document.getElementById('gesturesEnabledSelect'); |
3265 | 3332 | const swipeLeftSelect = document.getElementById('swipeLeftActionSelect'); |
3266 | 3333 | const swipeRightSelect = document.getElementById('swipeRightActionSelect'); |
@@ -3793,6 +3860,46 @@ async function saveSettings() { |
3793 | 3860 | localStorage.setItem('nym_translate_language', translateLangEl.value); |
3794 | 3861 | } |
3795 | 3862 |
|
| 3863 | + // Read and save app-wide UI language (already applied live via onchange, |
| 3864 | + // this just guarantees persistence if the select changed programmatically) |
| 3865 | + const uiLangEl = document.getElementById('uiLanguageSelect'); |
| 3866 | + if (uiLangEl) { |
| 3867 | + const uiLang = uiLangEl.value || ''; |
| 3868 | + if (uiLang !== (nym.getUiLanguage ? nym.getUiLanguage() : '')) { |
| 3869 | + if (typeof nym.applyUiLanguage === 'function') { |
| 3870 | + nym.applyUiLanguage(uiLang).catch(() => { }); |
| 3871 | + } |
| 3872 | + if (typeof nym._syncTranslateLanguageToUi === 'function') nym._syncTranslateLanguageToUi(uiLang); |
| 3873 | + } |
| 3874 | + nym.settings.uiLanguage = uiLang; |
| 3875 | + } |
| 3876 | + |
| 3877 | + // Read and save auto-translate settings |
| 3878 | + let autoTranslateChanged = false; |
| 3879 | + const autoTrEl = document.getElementById('autoTranslateSelect'); |
| 3880 | + if (autoTrEl) { |
| 3881 | + const on = autoTrEl.value === 'true'; |
| 3882 | + if (on !== !!nym.settings.autoTranslate) autoTranslateChanged = true; |
| 3883 | + nym.settings.autoTranslate = on; |
| 3884 | + localStorage.setItem('nym_auto_translate', String(on)); |
| 3885 | + } |
| 3886 | + const autoTrScope = [ |
| 3887 | + ['autoTranslateChannelsSelect', 'autoTranslateChannels', 'nym_auto_translate_channels'], |
| 3888 | + ['autoTranslatePMsSelect', 'autoTranslatePMs', 'nym_auto_translate_pms'], |
| 3889 | + ['autoTranslateGroupsSelect', 'autoTranslateGroups', 'nym_auto_translate_groups'], |
| 3890 | + ]; |
| 3891 | + for (const [elId, key, lsKey] of autoTrScope) { |
| 3892 | + const el = document.getElementById(elId); |
| 3893 | + if (!el) continue; |
| 3894 | + const on = el.value === 'true'; |
| 3895 | + if (on !== (nym.settings[key] !== false)) autoTranslateChanged = true; |
| 3896 | + nym.settings[key] = on; |
| 3897 | + localStorage.setItem(lsKey, String(on)); |
| 3898 | + } |
| 3899 | + if (autoTranslateChanged && typeof nym.retranslateVisibleMessages === 'function') { |
| 3900 | + nym.retranslateVisibleMessages(); |
| 3901 | + } |
| 3902 | + |
3796 | 3903 | const VALID_SWIPE_ACTIONS = ['quote', 'translate', 'copy', 'react', 'zap', 'slap', 'hug', 'none']; |
3797 | 3904 | const gesturesEnabledEl = document.getElementById('gesturesEnabledSelect'); |
3798 | 3905 | if (gesturesEnabledEl) { |
@@ -4226,7 +4333,7 @@ function initWallpaperUI() { |
4226 | 4333 | } |
4227 | 4334 | } |
4228 | 4335 |
|
4229 | | -const NYMCHAT_VERSION = 'v3.72.519'; |
| 4336 | +const NYMCHAT_VERSION = 'v3.73.519'; |
4230 | 4337 |
|
4231 | 4338 | const BUILD_REPO = 'https://github.com/Spl0itable/NYM'; |
4232 | 4339 |
|
@@ -6691,6 +6798,37 @@ async function applyNostrSettings(s) { |
6691 | 6798 | if (typeof nym._renderTranslateDropdownList === 'function') nym._renderTranslateDropdownList(); |
6692 | 6799 | } |
6693 | 6800 |
|
| 6801 | + // App-wide UI language — adopt a language chosen on another device. |
| 6802 | + if (typeof s.uiLanguage === 'string' && s.uiLanguage !== (nym.getUiLanguage ? nym.getUiLanguage() : '')) { |
| 6803 | + if (typeof nym.applyUiLanguage === 'function') { |
| 6804 | + nym.applyUiLanguage(s.uiLanguage).catch(() => { }); |
| 6805 | + } else { |
| 6806 | + nym.settings.uiLanguage = s.uiLanguage; |
| 6807 | + try { localStorage.setItem('nym_ui_language', s.uiLanguage); } catch (_) { } |
| 6808 | + } |
| 6809 | + if (typeof nym.populateUiLanguageSelect === 'function') nym.populateUiLanguageSelect(); |
| 6810 | + } |
| 6811 | + |
| 6812 | + // Auto-translate incoming messages (master + per-conversation-type gates) |
| 6813 | + if (typeof s.autoTranslate === 'boolean') { |
| 6814 | + nym.settings.autoTranslate = s.autoTranslate; |
| 6815 | + localStorage.setItem('nym_auto_translate', String(s.autoTranslate)); |
| 6816 | + } |
| 6817 | + if (typeof s.autoTranslateChannels === 'boolean') { |
| 6818 | + nym.settings.autoTranslateChannels = s.autoTranslateChannels; |
| 6819 | + localStorage.setItem('nym_auto_translate_channels', String(s.autoTranslateChannels)); |
| 6820 | + } |
| 6821 | + if (typeof s.autoTranslatePMs === 'boolean') { |
| 6822 | + nym.settings.autoTranslatePMs = s.autoTranslatePMs; |
| 6823 | + localStorage.setItem('nym_auto_translate_pms', String(s.autoTranslatePMs)); |
| 6824 | + } |
| 6825 | + if (typeof s.autoTranslateGroups === 'boolean') { |
| 6826 | + nym.settings.autoTranslateGroups = s.autoTranslateGroups; |
| 6827 | + localStorage.setItem('nym_auto_translate_groups', String(s.autoTranslateGroups)); |
| 6828 | + } |
| 6829 | + if (typeof nym._syncAutoTranslateSettingsUI === 'function') nym._syncAutoTranslateSettingsUI(); |
| 6830 | + if (typeof nym.retranslateVisibleMessages === 'function') nym.retranslateVisibleMessages(); |
| 6831 | + |
6694 | 6832 | // Favorite custom emoji packs |
6695 | 6833 | if (Array.isArray(s.emojiPackFavorites)) { |
6696 | 6834 | nym._emojiPackFavorites = s.emojiPackFavorites.filter(k => typeof k === 'string'); |
|
0 commit comments