mirror of
https://github.com/Jonnyan404/memos-bber.git
synced 2026-04-25 03:58:37 +09:00
ed5fc86e39
Replace the old <select> language picker with a custom toggle button and dropdown menu. Updates include new popup HTML (langToggle button, langMenu with menuitemradio buttons), new CSS for .lang-toggle/.lang-menu/.lang-menu-item (styling, hover/active states, shadow, rounded corners), and JS to manage label sync, menu state, click handlers, outside-click and Escape to close, and applying the selected UI language. Also update references in i18n code (setTitle target and language init flow) and persist language selection. Bump manifest version from 2026.03.24 to 2026.03.25.
205 lines
6.0 KiB
JavaScript
205 lines
6.0 KiB
JavaScript
const UI_LANGUAGE_STORAGE_KEY = 'uiLanguage'
|
|
|
|
const SUPPORTED_UI_LANGUAGES = new Set(['auto', 'en', 'zh_CN', 'ja', 'ko'])
|
|
|
|
function normalizeUiLanguage(value) {
|
|
const lang = String(value || 'auto')
|
|
return SUPPORTED_UI_LANGUAGES.has(lang) ? lang : 'auto'
|
|
}
|
|
|
|
function storageSyncGet(defaults) {
|
|
return new Promise((resolve) => {
|
|
chrome.storage.sync.get(defaults, (items) => resolve(items || {}))
|
|
})
|
|
}
|
|
|
|
function storageSyncSet(items) {
|
|
return new Promise((resolve) => {
|
|
chrome.storage.sync.set(items, () => resolve())
|
|
})
|
|
}
|
|
|
|
async function loadLocaleMessages(locale) {
|
|
if (!locale || locale === 'auto') return null
|
|
try {
|
|
const url = chrome.runtime.getURL(`_locales/${locale}/messages.json`)
|
|
const resp = await fetch(url)
|
|
if (!resp.ok) return null
|
|
return await resp.json()
|
|
} catch (_) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function formatSubstitutions(message, substitutions) {
|
|
if (!message) return ''
|
|
if (substitutions == null) return message
|
|
const subs = Array.isArray(substitutions) ? substitutions : [substitutions]
|
|
let out = message
|
|
for (let i = 0; i < subs.length; i++) {
|
|
const v = String(subs[i])
|
|
out = out.replaceAll(`$${i + 1}`, v)
|
|
out = out.replace('%s', v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
let currentUiLanguage = 'auto'
|
|
let overrideMessages = null
|
|
|
|
function getLanguageToggleLabel(lang) {
|
|
if (lang === 'en') return 'EN'
|
|
if (lang === 'zh_CN') return '中'
|
|
if (lang === 'ja') return '日'
|
|
if (lang === 'ko') return '한'
|
|
return 'A'
|
|
}
|
|
|
|
function syncLanguageToggleText(lang) {
|
|
const text = document.getElementById('langToggleText')
|
|
if (text) text.textContent = getLanguageToggleLabel(lang)
|
|
}
|
|
|
|
function syncLanguageMenuState(lang) {
|
|
const items = document.querySelectorAll('.lang-menu-item')
|
|
items.forEach((item) => {
|
|
const isActive = item.getAttribute('data-lang') === lang
|
|
item.classList.toggle('active', isActive)
|
|
item.setAttribute('aria-checked', isActive ? 'true' : 'false')
|
|
})
|
|
}
|
|
|
|
function setLanguageMenuOpen(isOpen) {
|
|
const toggle = document.getElementById('langToggle')
|
|
const menu = document.getElementById('langMenu')
|
|
if (!toggle || !menu) return
|
|
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false')
|
|
menu.classList.toggle('hidden', !isOpen)
|
|
}
|
|
|
|
function t(key, substitutions) {
|
|
const msg = overrideMessages && overrideMessages[key] && overrideMessages[key].message
|
|
if (typeof msg === 'string' && msg.length > 0) {
|
|
return formatSubstitutions(msg, substitutions)
|
|
}
|
|
const chromeMsg = chrome.i18n.getMessage(key, substitutions) || ''
|
|
return formatSubstitutions(chromeMsg, substitutions)
|
|
}
|
|
|
|
function setText(id, messageKey) {
|
|
const el = document.getElementById(id)
|
|
if (el) el.textContent = t(messageKey)
|
|
}
|
|
|
|
function setPlaceholder(id, messageKey) {
|
|
const el = document.getElementById(id)
|
|
if (el) el.placeholder = t(messageKey)
|
|
}
|
|
|
|
function setTitle(id, messageKey) {
|
|
const el = document.getElementById(id)
|
|
if (el) el.title = t(messageKey)
|
|
}
|
|
|
|
function applyStaticI18n() {
|
|
setText('saveKey', 'saveBtn')
|
|
setText('saveTag', 'saveBtn')
|
|
|
|
setText('supportedMemosVersion', 'supportedMemosVersion')
|
|
|
|
setPlaceholder('apiUrl', 'placeApiUrl')
|
|
setPlaceholder('apiTokens', 'placeApiTokens')
|
|
setPlaceholder('content', 'placeContent')
|
|
|
|
setText('lockPrivate', 'lockPrivate')
|
|
setText('lockProtected', 'lockProtected')
|
|
setText('lockPublic', 'lockPublic')
|
|
|
|
setText('content_submit_text', 'submitBtn')
|
|
const fullscreen = document.getElementById('fullscreen')
|
|
if (fullscreen) fullscreen.setAttribute('aria-label', t('tipFullscreen'))
|
|
|
|
setPlaceholder('hideInput', 'placeHideInput')
|
|
setPlaceholder('showInput', 'placeShowInput')
|
|
|
|
setText('uploadlist-title', 'uploadedListTitle')
|
|
|
|
// Language switcher
|
|
setText('langOptionAuto', 'langAuto')
|
|
setText('langOptionEn', 'langEnglish')
|
|
setText('langOptionZhCN', 'langChineseSimplified')
|
|
setText('langOptionJa', 'langJapanese')
|
|
setText('langOptionKo', 'langKorean')
|
|
setTitle('langToggle', 'tipLanguage')
|
|
const langToggle = document.getElementById('langToggle')
|
|
if (langToggle) langToggle.setAttribute('aria-label', t('tipLanguage'))
|
|
|
|
// Native hover tooltips (title)
|
|
setTitle('opensite', 'tipOpenSite')
|
|
setTitle('blog_info_edit', 'tipSettings')
|
|
setTitle('tags', 'tipTags')
|
|
setTitle('newtodo', 'tipTodo')
|
|
setTitle('upres', 'tipUpload')
|
|
setTitle('getlink', 'tipLink')
|
|
setTitle('random', 'tipRandom')
|
|
setTitle('search', 'tipSearch')
|
|
setTitle('lock', 'tipVisibility')
|
|
setTitle('content_submit_text', 'tipSend')
|
|
setTitle('fullscreen', 'tipFullscreen')
|
|
setTitle('editor-resize-handle', 'tipResize')
|
|
}
|
|
|
|
async function setUiLanguage(nextLang, { persist = true } = {}) {
|
|
const lang = normalizeUiLanguage(nextLang)
|
|
currentUiLanguage = lang
|
|
overrideMessages = await loadLocaleMessages(lang)
|
|
applyStaticI18n()
|
|
syncLanguageToggleText(lang)
|
|
syncLanguageMenuState(lang)
|
|
|
|
if (persist) await storageSyncSet({ [UI_LANGUAGE_STORAGE_KEY]: lang })
|
|
window.dispatchEvent(new CustomEvent('i18n:changed', { detail: { lang } }))
|
|
}
|
|
|
|
async function initLanguageSwitcher() {
|
|
const switcher = document.getElementById('lang_switcher')
|
|
const toggle = document.getElementById('langToggle')
|
|
const langItems = document.querySelectorAll('.lang-menu-item')
|
|
|
|
if (toggle) {
|
|
toggle.addEventListener('click', (event) => {
|
|
event.stopPropagation()
|
|
const isOpen = toggle.getAttribute('aria-expanded') === 'true'
|
|
setLanguageMenuOpen(!isOpen)
|
|
})
|
|
}
|
|
|
|
langItems.forEach((item) => {
|
|
item.addEventListener('click', async (event) => {
|
|
event.stopPropagation()
|
|
setLanguageMenuOpen(false)
|
|
await setUiLanguage(item.getAttribute('data-lang'))
|
|
})
|
|
})
|
|
|
|
document.addEventListener('click', (event) => {
|
|
if (!switcher || switcher.contains(event.target)) return
|
|
setLanguageMenuOpen(false)
|
|
})
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Escape') setLanguageMenuOpen(false)
|
|
})
|
|
|
|
const storedItems = await storageSyncGet({ [UI_LANGUAGE_STORAGE_KEY]: 'auto' })
|
|
const stored = normalizeUiLanguage(storedItems[UI_LANGUAGE_STORAGE_KEY])
|
|
await setUiLanguage(stored, { persist: false })
|
|
setLanguageMenuOpen(false)
|
|
}
|
|
|
|
window.t = t
|
|
window.setUiLanguage = setUiLanguage
|
|
window.getUiLanguage = () => currentUiLanguage
|
|
|
|
applyStaticI18n()
|
|
window.i18nReady = initLanguageSwitcher() |