feat: add language selector with i18n support

- Add createLanguageSelector function to amipro_utils.js
- Add language selector CSS styles to modern.css and core.css
- Support EN/JA language switching with localStorage persistence
This commit is contained in:
qingjie.du
2026-06-21 12:23:26 +09:00
parent d06b4cf629
commit 97a3a007e6
3 changed files with 251 additions and 4 deletions

View File

@@ -4,16 +4,60 @@
'use strict'; 'use strict';
var _currentLang = null;
var _i18n_map = null;
function getCurrentLanguage() {
if (_currentLang) return _currentLang;
var stored = localStorage.getItem('amipro_lang');
if (stored) { _currentLang = stored; return stored; }
var browserLang = window.navigator.language || 'en';
if (browserLang.indexOf('ja') === 0) return 'ja';
return 'en';
}
function setLanguage(lang) {
_currentLang = lang;
localStorage.setItem('amipro_lang', lang);
document.documentElement.lang = lang;
if (_i18n_map) setI18NText(_i18n_map);
updateImages(lang);
var sel = document.getElementById('lang-selector');
if (sel) sel.value = lang;
}
function updateImages(lang) {
var heroImg = document.getElementById('hero-image');
var searchImg = document.getElementById('search-image');
if (heroImg) {
if (lang === 'ja') {
heroImg.src = 'contextwizard-files/cw-banner-jp.png';
} else {
heroImg.src = 'contextwizard-files/screen-platforms-640-400.jpg';
}
}
if (searchImg) {
if (lang === 'ja') {
searchImg.src = 'contextwizard-files/screen-search-640-400-jp.jpg';
} else {
searchImg.src = 'contextwizard-files/screen-search-640-400.jpg';
}
}
}
function setI18NText(i18n_map){ function setI18NText(i18n_map){
_i18n_map = i18n_map;
var lang = getCurrentLanguage();
for (const key of i18n_map.keys()) { for (const key of i18n_map.keys()) {
const elm = $("#"+key); const elm = $("#"+key);
if(elm){ if(elm){
const lang = window.navigator.language;
var elem = i18n_map.get(key) var elem = i18n_map.get(key)
var msg = null var msg = null
if(elem){ if(elem){
msg = elem.get(lang) msg = elem.get(lang)
if(!msg && lang && lang.indexOf('-') > -1) msg = elem.get(lang.split('-')[0]); if(!msg) msg = elem.get('en');
if(!msg) msg = elem.get('en-US'); if(!msg) msg = elem.get('en-US');
} }
if(!msg)msg = key+"-"+lang if(!msg)msg = key+"-"+lang
@@ -24,12 +68,12 @@ function setI18NText(i18n_map){
} }
function getI18NText(i18n_map, key){ function getI18NText(i18n_map, key){
const lang = window.navigator.language; var lang = getCurrentLanguage();
var elem = i18n_map.get(key) var elem = i18n_map.get(key)
var msg = null var msg = null
if(elem){ if(elem){
msg = elem.get(lang) msg = elem.get(lang)
if(!msg && lang && lang.indexOf('-') > -1) msg = elem.get(lang.split('-')[0]); if(!msg) msg = elem.get('en');
if(!msg) msg = elem.get('en-US'); if(!msg) msg = elem.get('en-US');
} }
if(!msg)msg = key+"-"+lang if(!msg)msg = key+"-"+lang
@@ -37,6 +81,106 @@ function getI18NText(i18n_map, key){
return msg return msg
} }
function createLanguageSelector() {
var current = getCurrentLanguage();
var wrapper = document.createElement('div');
wrapper.className = 'lang-selector-wrapper lang-selector-floating';
wrapper.id = 'lang-selector-draggable';
wrapper.innerHTML = '<select id="lang-selector" class="lang-selector" onchange="setLanguage(this.value)">' +
'<option value="en"' + (current === 'en' ? ' selected' : '') + '>EN</option>' +
'<option value="ja"' + (current === 'ja' ? ' selected' : '') + '>JA</option>' +
'</select>';
var savedPos = localStorage.getItem('amipro_lang_pos');
if (savedPos) {
try {
var pos = JSON.parse(savedPos);
wrapper.style.left = pos.x + 'px';
wrapper.style.top = pos.y + 'px';
wrapper.style.right = 'auto';
} catch(e) {}
}
makeDraggable(wrapper);
return wrapper;
}
function makeDraggable(el) {
var isDragging = false;
var startX, startY, startLeft, startTop;
el.style.cursor = 'grab';
el.addEventListener('mousedown', function(e) {
if (e.target.tagName === 'SELECT') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
startLeft = el.offsetLeft;
startTop = el.offsetTop;
el.style.cursor = 'grabbing';
el.style.right = 'auto';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
var dx = e.clientX - startX;
var dy = e.clientY - startY;
var newLeft = startLeft + dx;
var newTop = startTop + dy;
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - el.offsetWidth));
newTop = Math.max(0, Math.min(newTop, window.innerHeight - el.offsetHeight));
el.style.left = newLeft + 'px';
el.style.top = newTop + 'px';
});
document.addEventListener('mouseup', function() {
if (!isDragging) return;
isDragging = false;
el.style.cursor = 'grab';
localStorage.setItem('amipro_lang_pos', JSON.stringify({
x: el.offsetLeft,
y: el.offsetTop
}));
});
el.addEventListener('touchstart', function(e) {
if (e.target.tagName === 'SELECT') return;
isDragging = true;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
startLeft = el.offsetLeft;
startTop = el.offsetTop;
el.style.right = 'auto';
}, { passive: true });
document.addEventListener('touchmove', function(e) {
if (!isDragging) return;
var dx = e.touches[0].clientX - startX;
var dy = e.touches[0].clientY - startY;
var newLeft = startLeft + dx;
var newTop = startTop + dy;
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - el.offsetWidth));
newTop = Math.max(0, Math.min(newTop, window.innerHeight - el.offsetHeight));
el.style.left = newLeft + 'px';
el.style.top = newTop + 'px';
}, { passive: true });
document.addEventListener('touchend', function() {
if (!isDragging) return;
isDragging = false;
localStorage.setItem('amipro_lang_pos', JSON.stringify({
x: el.offsetLeft,
y: el.offsetTop
}));
});
}
function initRevealAnimations(){ function initRevealAnimations(){
const animated = document.querySelectorAll('.reveal, [data-animate]'); const animated = document.querySelectorAll('.reveal, [data-animate]');
if(!animated || animated.length === 0){ if(!animated || animated.length === 0){

File diff suppressed because one or more lines are too long

View File

@@ -400,3 +400,55 @@ body {
left: -38px; left: -38px;
} }
} }
.lang-selector-wrapper {
margin-left: 12px;
background: var(--bg-panel);
border: 1px solid var(--border-soft);
border-radius: 8px;
padding: 4px;
backdrop-filter: blur(16px);
display: inline-block;
}
.lang-selector-floating {
position: fixed;
top: 16px;
right: 16px;
z-index: 9999;
margin-left: 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: box-shadow 0.2s ease;
display: inline-block;
width: fit-content;
max-width: fit-content;
flex-shrink: 0;
white-space: nowrap;
}
.lang-selector-floating:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
.lang-selector {
background: transparent;
border: 1px solid var(--border-soft);
border-radius: 8px;
color: var(--text-bright);
padding: 4px 8px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
outline: none;
appearance: none;
-webkit-appearance: none;
}
.lang-selector option {
background: #1e293b;
color: var(--text-bright);
}
.lang-selector:hover {
border-color: var(--brand-primary);
}