feat: add Korean-English localization and light theme
Localize studio, curriculum, visualization and loading interfaces. Add persistent language and theme settings, refine module loading states, and normalize remote shell locales.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
const storageKey = 'autonomy-studio.lms.language';
|
||||
let language = 'ko';
|
||||
try { language = localStorage.getItem(storageKey) === 'en' ? 'en' : 'ko'; } catch {}
|
||||
|
||||
const english = {
|
||||
'벽 배치': 'Place walls',
|
||||
'맵 저장': 'Save map',
|
||||
'로봇 경로 표시': 'Show robot path',
|
||||
'센서 표시': 'Show sensors',
|
||||
'초기 위치 지정': 'Set initial pose',
|
||||
'목표 위치 지정': 'Set goal',
|
||||
'카메라 시점': 'Camera view',
|
||||
'미니맵 접기/펼치기': 'Toggle minimap',
|
||||
'미니맵 접기': 'Collapse minimap',
|
||||
'미니맵 펼치기': 'Expand minimap',
|
||||
'카메라 화면 접기/펼치기': 'Toggle camera preview',
|
||||
'카메라 화면 접기': 'Collapse camera preview',
|
||||
'카메라 화면 펼치기': 'Expand camera preview',
|
||||
'실시간 카메라 화면': 'Live camera preview',
|
||||
'i 전진 · , 후진 · j/l 회전 · k 정지': 'i Forward · , Reverse · j/l Turn · k Stop',
|
||||
'i 전진 · , 후진 · j/l 주행 중 조향 · k 정지': 'i Forward · , Reverse · j/l Steer while moving · k Stop',
|
||||
'ROS 상태 접기/펼치기': 'Toggle ROS status',
|
||||
'ROS 상태 접기': 'Collapse ROS status',
|
||||
'ROS 상태 펼치기': 'Expand ROS status',
|
||||
'ROS 상태': 'ROS status',
|
||||
'응답을 기다리는 중입니다.': 'Waiting for a response.',
|
||||
'맵을 저장하고 있습니다.': 'Saving the map.',
|
||||
'가제보 로봇 위치를 적용하지 못했습니다.': 'Failed to set the Gazebo robot pose.',
|
||||
'주행 시뮬레이션에서는 초기 위치를 한 번만 설정할 수 있습니다.': 'The initial pose can only be set once during navigation simulation.',
|
||||
'로봇 초기 위치 설정': 'Set initial robot pose',
|
||||
'3D 포인트클라우드를 표시 중입니다.': 'Displaying the 3D point cloud.',
|
||||
'모듈을 연결해주세요.': 'Connect your modules.',
|
||||
'맵 저장에 실패했습니다.': 'Failed to save the map.',
|
||||
'기존 목표를 취소하지 못했습니다.': 'Failed to cancel the previous goal.',
|
||||
'가제보 로봇 위치 적용 시간이 초과되었습니다.': 'Setting the Gazebo robot pose timed out.'
|
||||
};
|
||||
|
||||
export function t(key) { return language === 'en' ? (english[key] ?? key) : key; }
|
||||
|
||||
// Only explicitly marked viewer UI is translated. Topic names and telemetry stay intact.
|
||||
export function setText(element, key) {
|
||||
element.dataset.i18n = key;
|
||||
element.textContent = t(key);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
document.documentElement.lang = language;
|
||||
document.querySelectorAll('[data-i18n]').forEach(element => {
|
||||
element.textContent = t(element.dataset.i18n);
|
||||
});
|
||||
for (const attribute of ['title', 'aria-label', 'alt']) {
|
||||
document.querySelectorAll(`[data-i18n-${attribute}]`).forEach(element => {
|
||||
element.setAttribute(attribute, t(element.getAttribute(`data-i18n-${attribute}`)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('storage', event => {
|
||||
if (event.key !== storageKey) return;
|
||||
language = event.newValue === 'en' ? 'en' : 'ko';
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
|
||||
// Embedded viewers may not share storage with the app (for example file URLs).
|
||||
window.addEventListener('message', event => {
|
||||
if (window.parent === window || event.source !== window.parent) return;
|
||||
if (event.data?.type !== 'lms:language' || !['ko', 'en'].includes(event.data.language)) return;
|
||||
language = event.data.language;
|
||||
refresh();
|
||||
});
|
||||
if (window.parent !== window) window.parent.postMessage({ type: 'lms:language:ready' }, '*');
|
||||
Reference in New Issue
Block a user