chore: initialize insar management system v2
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export const I18nContext = createContext(null);
|
||||
|
||||
export const useI18n = () => {
|
||||
const context = useContext(I18nContext);
|
||||
if (!context) {
|
||||
throw new Error('useI18n must be used within I18nProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,209 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { I18nContext } from './I18nContext';
|
||||
import { SUPPORTED_LANGUAGES, translateText } from './translations';
|
||||
|
||||
const STORAGE_KEY = 'ims_ui_language';
|
||||
|
||||
const isSupportedLanguage = (value) => SUPPORTED_LANGUAGES.includes(value);
|
||||
|
||||
const getInitialLanguage = () => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (isSupportedLanguage(stored)) return stored;
|
||||
return 'zh';
|
||||
};
|
||||
|
||||
const shouldSkipNode = (node) => {
|
||||
if (!node || !node.parentElement) return true;
|
||||
const parent = node.parentElement;
|
||||
if (parent.closest('[data-no-i18n="true"]')) return true;
|
||||
const tag = parent.tagName;
|
||||
if (!tag) return true;
|
||||
return tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA' || tag === 'CODE' || tag === 'PRE';
|
||||
};
|
||||
|
||||
const getNodeSourceText = (textSourceMap, node) => {
|
||||
if (!textSourceMap.has(node)) {
|
||||
textSourceMap.set(node, node.nodeValue ?? '');
|
||||
}
|
||||
return textSourceMap.get(node);
|
||||
};
|
||||
|
||||
const setNodeSourceText = (textSourceMap, node, sourceText) => {
|
||||
if (!node) return;
|
||||
textSourceMap.set(node, sourceText ?? '');
|
||||
};
|
||||
|
||||
const getAttrSourceMap = (attrSourceMap, element) => {
|
||||
if (!attrSourceMap.has(element)) {
|
||||
attrSourceMap.set(element, new Map());
|
||||
}
|
||||
return attrSourceMap.get(element);
|
||||
};
|
||||
|
||||
const getAttrSourceText = (attrSourceMap, element, attr, fallbackValue) => {
|
||||
const elementMap = getAttrSourceMap(attrSourceMap, element);
|
||||
if (!elementMap.has(attr)) {
|
||||
elementMap.set(attr, fallbackValue ?? '');
|
||||
}
|
||||
return elementMap.get(attr);
|
||||
};
|
||||
|
||||
const setAttrSourceText = (attrSourceMap, element, attr, sourceText) => {
|
||||
if (!element) return;
|
||||
const elementMap = getAttrSourceMap(attrSourceMap, element);
|
||||
elementMap.set(attr, sourceText ?? '');
|
||||
};
|
||||
|
||||
const translateElementAttrs = (element, language, attrSourceMap) => {
|
||||
if (!element || element.closest('[data-no-i18n="true"]')) return;
|
||||
['placeholder', 'title', 'aria-label'].forEach((attr) => {
|
||||
const value = element.getAttribute(attr);
|
||||
if (!value) return;
|
||||
const source = getAttrSourceText(attrSourceMap, element, attr, value);
|
||||
const translated = translateText(source, language);
|
||||
if (translated !== value) {
|
||||
element.setAttribute(attr, translated);
|
||||
}
|
||||
});
|
||||
|
||||
if (element.tagName === 'INPUT') {
|
||||
const type = (element.getAttribute('type') || '').toLowerCase();
|
||||
if (type === 'button' || type === 'submit' || type === 'reset') {
|
||||
const value = element.getAttribute('value');
|
||||
if (!value) return;
|
||||
const source = getAttrSourceText(attrSourceMap, element, 'value', value);
|
||||
const translated = translateText(source, language);
|
||||
if (translated !== value) {
|
||||
element.setAttribute('value', translated);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const translateSubtree = (root, language, textSourceMap, attrSourceMap) => {
|
||||
if (!root) return;
|
||||
|
||||
if (root.nodeType === Node.ELEMENT_NODE) {
|
||||
translateElementAttrs(root, language, attrSourceMap);
|
||||
}
|
||||
|
||||
if (root.nodeType === Node.TEXT_NODE) {
|
||||
if (!shouldSkipNode(root)) {
|
||||
const source = getNodeSourceText(textSourceMap, root);
|
||||
const translated = translateText(source, language);
|
||||
if (translated !== root.nodeValue) {
|
||||
root.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||
let current = walker.nextNode();
|
||||
while (current) {
|
||||
if (!shouldSkipNode(current)) {
|
||||
const source = getNodeSourceText(textSourceMap, current);
|
||||
const translated = translateText(source, language);
|
||||
if (translated !== current.nodeValue) {
|
||||
current.nodeValue = translated;
|
||||
}
|
||||
}
|
||||
current = walker.nextNode();
|
||||
}
|
||||
|
||||
if (root.querySelectorAll) {
|
||||
root.querySelectorAll('*').forEach((element) => {
|
||||
translateElementAttrs(element, language, attrSourceMap);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const I18nProvider = ({ children }) => {
|
||||
const [language, setLanguageState] = useState(getInitialLanguage);
|
||||
const textSourceMapRef = useRef(new WeakMap());
|
||||
const attrSourceMapRef = useRef(new WeakMap());
|
||||
|
||||
const setLanguage = useCallback((nextLanguage) => {
|
||||
if (!isSupportedLanguage(nextLanguage)) return;
|
||||
setLanguageState(nextLanguage);
|
||||
}, []);
|
||||
|
||||
const t = useCallback((text) => translateText(text, language), [language]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, language);
|
||||
document.documentElement.setAttribute('lang', language === 'en' ? 'en-US' : 'zh-CN');
|
||||
}, [language]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!document?.body) return undefined;
|
||||
let isMutating = false;
|
||||
|
||||
const safeTranslate = (node) => {
|
||||
if (!node) return;
|
||||
isMutating = true;
|
||||
try {
|
||||
translateSubtree(node, language, textSourceMapRef.current, attrSourceMapRef.current);
|
||||
} finally {
|
||||
isMutating = false;
|
||||
}
|
||||
};
|
||||
|
||||
safeTranslate(document.body);
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (isMutating) return;
|
||||
const roots = new Set();
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'characterData') {
|
||||
setNodeSourceText(textSourceMapRef.current, mutation.target, mutation.target?.nodeValue ?? '');
|
||||
const parent = mutation.target?.parentElement;
|
||||
if (parent) roots.add(parent);
|
||||
}
|
||||
if (mutation.type === 'attributes' && mutation.attributeName) {
|
||||
const targetElement = mutation.target;
|
||||
const attrName = mutation.attributeName;
|
||||
const attrValue = targetElement.getAttribute(attrName);
|
||||
setAttrSourceText(attrSourceMapRef.current, targetElement, attrName, attrValue ?? '');
|
||||
roots.add(targetElement);
|
||||
}
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
roots.add(node);
|
||||
} else if (node.nodeType === Node.TEXT_NODE) {
|
||||
setNodeSourceText(textSourceMapRef.current, node, node.nodeValue ?? '');
|
||||
if (node.parentElement) {
|
||||
roots.add(node.parentElement);
|
||||
}
|
||||
} else if (node.parentElement) {
|
||||
roots.add(node.parentElement);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (roots.size === 0) return;
|
||||
roots.forEach((node) => safeTranslate(node));
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['placeholder', 'title', 'aria-label', 'value'],
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [language]);
|
||||
|
||||
const value = useMemo(() => ({
|
||||
language,
|
||||
setLanguage,
|
||||
t,
|
||||
}), [language, setLanguage, t]);
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={value}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,420 @@
|
||||
const TRANSLATION_PAIRS = [
|
||||
{ zh: '正在检查登录状态...', en: 'Checking login status...' },
|
||||
{ zh: '请稍候,系统正在验证会话。', en: 'Please wait, verifying your session.' },
|
||||
{ zh: 'InSAR 自动化管理系统', en: 'InSAR Automation Management System' },
|
||||
{ zh: '科研工程模式', en: 'Research Engineering Mode' },
|
||||
{ zh: '已授权', en: 'Licensed' },
|
||||
{ zh: '未授权', en: 'Unlicensed' },
|
||||
{ zh: '管理员', en: 'Admin' },
|
||||
{ zh: '只读账号', en: 'Read-only account' },
|
||||
{ zh: '任务', en: 'Tasks' },
|
||||
{ zh: '授权至', en: 'License until' },
|
||||
{ zh: '刷新自检状态', en: 'Refresh health status' },
|
||||
{ zh: '自检中...', en: 'Checking...' },
|
||||
{ zh: '刷新自检', en: 'Refresh Health' },
|
||||
{ zh: '退出登录', en: 'Sign Out' },
|
||||
|
||||
{ zh: '数据管理', en: 'Data' },
|
||||
{ zh: '生产规划', en: 'Production' },
|
||||
{ zh: 'D-InSAR 分析', en: 'D-InSAR Analysis' },
|
||||
{ zh: '水体监测', en: 'Water Monitoring' },
|
||||
{ zh: '运行维护', en: 'Operations' },
|
||||
|
||||
{ zh: '入库监控', en: 'Ingest Monitor' },
|
||||
{ zh: '数据列表', en: 'Data List' },
|
||||
{ zh: '灾害点', en: 'Hazard Points' },
|
||||
{ zh: '配对规划', en: 'Pair Planning' },
|
||||
{ zh: '任务规划', en: 'Task Planning' },
|
||||
{ zh: '任务批次', en: 'Task Batches' },
|
||||
{ zh: '数据分发', en: 'Data Distribution' },
|
||||
{ zh: 'D-InSAR生产', en: 'D-InSAR Production' },
|
||||
{ zh: 'D-InSAR 结果', en: 'D-InSAR Results' },
|
||||
{ zh: 'AI 质量评估', en: 'AI Quality Assessment' },
|
||||
{ zh: '运维自检', en: 'Health Check' },
|
||||
{ zh: '用户管理', en: 'User Management' },
|
||||
{ zh: '审计日志', en: 'Audit Logs' },
|
||||
|
||||
{ zh: '刷新源数据', en: 'Refresh Source Data' },
|
||||
{ zh: '统计', en: 'Statistics' },
|
||||
{ zh: '源数据检索', en: 'Source Data Search' },
|
||||
{ zh: '卫星:全部', en: 'Satellite: All' },
|
||||
{ zh: '成像模式:全部', en: 'Imaging Mode: All' },
|
||||
{ zh: '成像时间起:不限', en: 'Imaging Date From: Any' },
|
||||
{ zh: '成像时间止:不限', en: 'Imaging Date To: Any' },
|
||||
{ zh: '极化方式:全部', en: 'Polarization: All' },
|
||||
{ zh: '产品级别:全部', en: 'Product Level: All' },
|
||||
{ zh: 'AOI: 不限', en: 'AOI: Any' },
|
||||
{ zh: 'AOI: 行政区', en: 'AOI: Region' },
|
||||
{ zh: 'AOI: 上传SHP', en: 'AOI: Upload SHP' },
|
||||
{ zh: '搜索', en: 'Search' },
|
||||
{ zh: '重置', en: 'Reset' },
|
||||
{ zh: '选择省份', en: 'Select Province' },
|
||||
{ zh: '选择地市(可选)', en: 'Select City (Optional)' },
|
||||
{ zh: '高级字段', en: 'Advanced Fields' },
|
||||
{ zh: '卫星模式:全部', en: 'Satellite Mode: All' },
|
||||
{ zh: '接收站:全部', en: 'Receiving Station: All' },
|
||||
{ zh: '轨道圈号:全部', en: 'Orbit Circle: All' },
|
||||
{ zh: '采集时间:全部', en: 'Acquisition Time: All' },
|
||||
{ zh: '产品类型:全部', en: 'Product Type: All' },
|
||||
{ zh: '产品唯一ID:全部', en: 'Product Unique ID: All' },
|
||||
{ zh: '轨道方向:全部', en: 'Orbit Direction: All' },
|
||||
{ zh: '有精轨:全部', en: 'Precise Orbit: All' },
|
||||
{ zh: '有精轨:是', en: 'Precise Orbit: Yes' },
|
||||
{ zh: '有精轨:否', en: 'Precise Orbit: No' },
|
||||
{ zh: 'ENVI已处理:全部', en: 'ENVI Processed: All' },
|
||||
{ zh: 'ENVI已处理:是', en: 'ENVI Processed: Yes' },
|
||||
{ zh: 'ENVI已处理:否', en: 'ENVI Processed: No' },
|
||||
{ zh: '源数据检索选项加载中...', en: 'Loading source data search options...' },
|
||||
{ zh: '无源数据', en: 'No source data.' },
|
||||
|
||||
{ zh: '覆盖面全部显示/隐藏', en: 'Toggle all coverage' },
|
||||
{ zh: '源影像一键显示', en: 'Show All Source Previews' },
|
||||
{ zh: '源影像一键隐藏', en: 'Hide All Source Previews' },
|
||||
{ zh: '上一页', en: 'Previous' },
|
||||
{ zh: '下一页', en: 'Next' },
|
||||
{ zh: '每页', en: 'Per page' },
|
||||
{ zh: '条', en: 'items' },
|
||||
{ zh: '跳到', en: 'Go to' },
|
||||
{ zh: '页', en: 'page' },
|
||||
{ zh: '跳转', en: 'Jump' },
|
||||
{ zh: '提示:可跳转页码范围为', en: 'Tip: valid page range is' },
|
||||
{ zh: '按 Enter 可快速跳转。', en: 'Press Enter to jump.' },
|
||||
|
||||
{ zh: '未找到 Dinsar 结果。', en: 'No D-InSAR results found.' },
|
||||
{ zh: '清除筛选', en: 'Clear Filter' },
|
||||
{ zh: '一键显示', en: 'Show All' },
|
||||
{ zh: '一键隐藏', en: 'Hide All' },
|
||||
{ zh: '隐藏日期', en: 'Hide Dates' },
|
||||
{ zh: '显示日期', en: 'Show Dates' },
|
||||
{ zh: 'AI 评分过滤', en: 'AI Score Filter' },
|
||||
{ zh: '过滤低于此分数的低质量结果', en: 'Filter low-quality results below this score' },
|
||||
{ zh: '主影像日期', en: 'Master date' },
|
||||
{ zh: '辅影像日期', en: 'Slave date' },
|
||||
{ zh: '标记为高质量', en: 'Mark as high quality' },
|
||||
{ zh: '标记为低质量', en: 'Mark as low quality' },
|
||||
{ zh: '良好', en: 'Good' },
|
||||
{ zh: '欠佳', en: 'Poor' },
|
||||
{ zh: '使用 AI 自动分析此结果', en: 'Use AI to analyze this result' },
|
||||
{ zh: 'AI 诊断', en: 'AI Diagnose' },
|
||||
{ zh: '在地图上显示/隐藏', en: 'Show/hide on map' },
|
||||
{ zh: 'AI 质量评分', en: 'AI quality score' },
|
||||
|
||||
{ zh: '影像信息', en: 'Image Information' },
|
||||
{ zh: '卫星:', en: 'Satellite:' },
|
||||
{ zh: '卫星模式:', en: 'Satellite Mode:' },
|
||||
{ zh: '接收站:', en: 'Receiving Station:' },
|
||||
{ zh: '成像日期:', en: 'Imaging Date:' },
|
||||
{ zh: '成像模式:', en: 'Imaging Mode:' },
|
||||
{ zh: '轨道圈号:', en: 'Orbit Circle:' },
|
||||
{ zh: '场景中心经度:', en: 'Scene Center Lon:' },
|
||||
{ zh: '场景中心纬度:', en: 'Scene Center Lat:' },
|
||||
{ zh: '采集时间:', en: 'Acquisition Time:' },
|
||||
{ zh: '产品类型:', en: 'Product Type:' },
|
||||
{ zh: '极化方式:', en: 'Polarization:' },
|
||||
{ zh: '产品级别:', en: 'Product Level:' },
|
||||
{ zh: '产品唯一ID:', en: 'Product Unique ID:' },
|
||||
{ zh: '轨道方向:', en: 'Orbit Direction:' },
|
||||
{ zh: '有精轨:', en: 'Has Orbit:' },
|
||||
{ zh: '轨道文件:', en: 'Orbit File:' },
|
||||
{ zh: 'ENVI已处理:', en: 'ENVI Processed:' },
|
||||
{ zh: '关闭', en: 'Close' },
|
||||
|
||||
{ zh: '是', en: 'Yes' },
|
||||
{ zh: '否', en: 'No' },
|
||||
{ zh: '未知', en: 'Unknown' },
|
||||
{ zh: '加载中', en: 'Loading' },
|
||||
{ zh: '失败', en: 'Failed' },
|
||||
{ zh: '成功', en: 'Succeeded' },
|
||||
|
||||
// AiQualityPanel
|
||||
{ zh: '基于标注结果训练随机森林模型,并对所有结果进行质量预测。', en: 'Train a random forest model based on labeled results and predict quality for all results.' },
|
||||
{ zh: '已标注', en: 'Labeled' },
|
||||
{ zh: '良好', en: 'Good' },
|
||||
{ zh: '欠佳', en: 'Poor' },
|
||||
{ zh: '处理中...', en: 'Processing...' },
|
||||
{ zh: '训练模型', en: 'Train Model' },
|
||||
{ zh: '至少需要2个样本 (包含好和坏) 才能训练。', en: 'At least 2 samples (good and bad) are required to train.' },
|
||||
{ zh: '预测所有结果', en: 'Predict All Results' },
|
||||
{ zh: '请先训练模型。', en: 'Please train the model first.' },
|
||||
|
||||
// PairingPanel
|
||||
{ zh: '基于时间基线、空间基线与重叠率筛选干涉对,可选 AOI 限定范围。', en: 'Filter interferometric pairs by temporal baseline, spatial baseline, and overlap ratio. Optional AOI constraint.' },
|
||||
{ zh: '配对', en: 'Pair' },
|
||||
{ zh: 'PS准备', en: 'PS Prep' },
|
||||
{ zh: '结果与刷新', en: 'Results & Refresh' },
|
||||
{ zh: '已生成配对', en: 'Generated Pairs' },
|
||||
{ zh: '已选中', en: 'Selected' },
|
||||
{ zh: '刷新当前检索', en: 'Refresh Current Search' },
|
||||
{ zh: '刷新结果', en: 'Refresh Results' },
|
||||
|
||||
// BatchPanel
|
||||
{ zh: '刷新中...', en: 'Refreshing...' },
|
||||
{ zh: '刷新批次', en: 'Refresh Batches' },
|
||||
{ zh: '-- 选择批次 --', en: '-- Select Batch --' },
|
||||
{ zh: '全部完成', en: 'Mark All Done' },
|
||||
{ zh: '请选择一个批次查看明细。', en: 'Select a batch to view details.' },
|
||||
{ zh: '该批次暂无明细。', en: 'No items in this batch.' },
|
||||
{ zh: '未审核', en: 'Pending' },
|
||||
{ zh: '审核中', en: 'In Review' },
|
||||
{ zh: '可下发', en: 'Approved' },
|
||||
{ zh: '不宜下发', en: 'Rejected' },
|
||||
{ zh: '备注', en: 'Remark' },
|
||||
{ zh: '保存', en: 'Save' },
|
||||
|
||||
// --- Base layers (appConstants) ---
|
||||
{ zh: 'Google 影像', en: 'Google Imagery' },
|
||||
{ zh: '高德 影像', en: 'Gaode Imagery' },
|
||||
{ zh: '高德 矢量', en: 'Gaode Vector' },
|
||||
|
||||
// --- Preview status (appUiHelpers) ---
|
||||
{ zh: '回退', en: 'Fallback' },
|
||||
{ zh: '纠正', en: 'Ortho' },
|
||||
{ zh: '未建', en: 'None' },
|
||||
|
||||
// --- App.jsx: map & region ---
|
||||
{ zh: '区域定位', en: 'Region Locator' },
|
||||
{ zh: '底图', en: 'Base Map' },
|
||||
{ zh: '日志', en: 'Logs' },
|
||||
{ zh: '暂无日志。', en: 'No logs.' },
|
||||
{ zh: '收起', en: 'Collapse' },
|
||||
{ zh: '当前定位', en: 'Current Location' },
|
||||
{ zh: '定位中...', en: 'Locating...' },
|
||||
{ zh: '定位到选中区域', en: 'Zoom to Region' },
|
||||
{ zh: '清除定位高亮', en: 'Clear Highlight' },
|
||||
{ zh: '可只选到省/市级进行定位。', en: 'Select province or city to locate.' },
|
||||
{ zh: '可只选到省/市级,系统将自动使用当前选中层级边界。', en: 'Select province or city level; the system will use the selected boundary.' },
|
||||
|
||||
// --- App.jsx: pairing modal ---
|
||||
{ zh: 'D-InSAR 配对参数', en: 'D-InSAR Pairing Parameters' },
|
||||
{ zh: '配对提示', en: 'Pairing Info' },
|
||||
{ zh: '回退模式', en: 'Fallback Mode' },
|
||||
{ zh: '已选择', en: 'Selected' },
|
||||
{ zh: '保存批次', en: 'Save Batch' },
|
||||
{ zh: '清空结果', en: 'Clear Results' },
|
||||
{ zh: '预览', en: 'Preview' },
|
||||
{ zh: '全选', en: 'Select All' },
|
||||
{ zh: '起始日期 (只考虑此日期后的影像)', en: 'Start Date (only images after this date)' },
|
||||
{ zh: 'AOI 来源:', en: 'AOI Source:' },
|
||||
{ zh: '上传SHP', en: 'Upload SHP' },
|
||||
{ zh: '行政区选择', en: 'Region Selection' },
|
||||
{ zh: '限定范围 (Shapefile,可选):', en: 'AOI Constraint (Shapefile, optional):' },
|
||||
{ zh: '选择文件...', en: 'Select File...' },
|
||||
{ zh: '行政区范围:', en: 'Region Boundary:' },
|
||||
{ zh: 'AOI 覆盖率阈值 (0 表示不限制):', en: 'AOI Coverage Threshold (0 = unlimited):' },
|
||||
{ zh: '成像模式一致', en: 'Consistent Imaging Mode' },
|
||||
{ zh: '极化一致', en: 'Consistent Polarization' },
|
||||
{ zh: '仅使用有精轨数据的影像', en: 'Only Precise-orbit Images' },
|
||||
{ zh: '开始配对', en: 'Start Pairing' },
|
||||
{ zh: '取消', en: 'Cancel' },
|
||||
|
||||
// --- App.jsx: PS modal ---
|
||||
{ zh: '准备PS时序数据栈', en: 'Prepare PS Time-series Stack' },
|
||||
{ zh: '研究区域来源:', en: 'Study Area Source:' },
|
||||
{ zh: '研究区域 (Shapefile):', en: 'Study Area (Shapefile):' },
|
||||
{ zh: '准备并导出', en: 'Prepare & Export' },
|
||||
|
||||
// --- App.jsx: province/city selects ---
|
||||
{ zh: '-- 省级 --', en: '-- Province --' },
|
||||
{ zh: '-- 地市 --', en: '-- City --' },
|
||||
|
||||
// --- App.jsx: misc UI ---
|
||||
{ zh: '仅管理员可访问用户管理。', en: 'Admin access only.' },
|
||||
{ zh: '仅管理员可访问审计日志。', en: 'Admin access only.' },
|
||||
{ zh: '当前账号为只读用户,无法执行写操作。', en: 'Read-only account, no write access.' },
|
||||
{ zh: '当前账号为只读权限,无法执行此操作。如需操作,请联系管理员升级权限。', en: 'Read-only account. Contact admin to request write access.' },
|
||||
{ zh: '任务已入队,等待处理...', en: 'Task queued, pending...' },
|
||||
|
||||
// --- IDLAutomationPanel ---
|
||||
{ zh: 'SARscape 处理中心', en: 'SARscape Processing Center' },
|
||||
{ zh: '已安装', en: 'Installed' },
|
||||
{ zh: '未检测到', en: 'Not Detected' },
|
||||
{ zh: '运行中', en: 'Running' },
|
||||
{ zh: '引擎', en: 'Engine' },
|
||||
{ zh: '任务运行中', en: 'Task Running' },
|
||||
{ zh: '强制解锁', en: 'Force Unlock' },
|
||||
{ zh: '输入管理员密码', en: 'Enter admin password' },
|
||||
{ zh: '确认取消', en: 'Confirm Cancel' },
|
||||
{ zh: '按钮已锁定,等待任务完成', en: 'Buttons locked, waiting for task completion' },
|
||||
{ zh: 'Task 状态总览', en: 'Task Status Overview' },
|
||||
{ zh: '加载中...', en: 'Loading...' },
|
||||
{ zh: '刷新', en: 'Refresh' },
|
||||
{ zh: '填写下方数据目录后点击刷新', en: 'Enter data directory below and click Refresh' },
|
||||
{ zh: '已导入', en: 'Imported' },
|
||||
{ zh: '已处理', en: 'Processed' },
|
||||
{ zh: '已提取', en: 'Extracted' },
|
||||
{ zh: '结构', en: 'Structure' },
|
||||
{ zh: '修改时间', en: 'Modified' },
|
||||
{ zh: 'Step 1: 数据导入 (Import)', en: 'Step 1: Data Import' },
|
||||
{ zh: '扫描目录下的 LuTan-1 原始数据 (*.meta.xml),转换为 ENVI SARscape 格式 (*.sml)。', en: 'Scan LuTan-1 raw data (*.meta.xml) and convert to ENVI SARscape format (*.sml).' },
|
||||
{ zh: '支持 Task_*/master|slave 结构和平铺目录。', en: 'Supports Task_*/master|slave structure and flat directories.' },
|
||||
{ zh: '数据目录 (root_dir)', en: 'Data Directory (root_dir)' },
|
||||
{ zh: '最大处理数', en: 'Max Items' },
|
||||
{ zh: '0 = 不限制', en: '0 = No limit' },
|
||||
{ zh: '预检查', en: 'Pre-check' },
|
||||
{ zh: '开始导入', en: 'Start Import' },
|
||||
{ zh: 'Import 检查结果', en: 'Import Check Results' },
|
||||
{ zh: 'Step 2: D-InSAR 生产', en: 'Step 2: D-InSAR Processing' },
|
||||
{ zh: '对 Task_* 目录执行 D-InSAR 位移生成。', en: 'Execute D-InSAR displacement processing on Task_* directories.' },
|
||||
{ zh: '未导入的数据将自动先执行 Import,再进行 D-InSAR 处理。', en: 'Unimported data will be auto-imported before D-InSAR processing.' },
|
||||
{ zh: 'DEM 路径由系统配置管理,无需手动指定。', en: 'DEM path is managed by system configuration.' },
|
||||
{ zh: '默认处理', en: 'Default Processing' },
|
||||
{ zh: '自定义处理', en: 'Custom Processing' },
|
||||
{ zh: '调用 ENVI SARscape metatask 一键执行全流程。参数:3×3 多视、默认自适应滤波、自动 GCP 精配准。', en: 'Runs ENVI SARscape metatask end-to-end: 3×3 multilooking, adaptive filtering, auto GCP co-registration.' },
|
||||
{ zh: '逐步调用各处理模块,参数可控。多视:动态计算、GOLDSTEIN 滤波、自动 GCP、相干阈值 coh=0.05。', en: 'Step-by-step modules: dynamic multilooking, Goldstein filtering, auto GCP, coherence threshold 0.05.' },
|
||||
{ zh: 'D-InSAR 检查结果', en: 'D-InSAR Check Results' },
|
||||
{ zh: 'Step 3: 结果提取', en: 'Step 3: Result Extraction' },
|
||||
{ zh: '将 D-InSAR 生产结果中的 disp 文件提取到扫描目录,以便系统识别。', en: 'Extract displacement files from D-InSAR results to the scan directory.' },
|
||||
{ zh: '提取 Disp 结果', en: 'Extract Disp Results' },
|
||||
{ zh: '目标目录', en: 'Target Directory' },
|
||||
{ zh: '新增', en: 'Added' },
|
||||
{ zh: '更新', en: 'Updated' },
|
||||
{ zh: '跳过', en: 'Skipped' },
|
||||
{ zh: '处理', en: 'Processed' },
|
||||
{ zh: '任务历史', en: 'Run History' },
|
||||
{ zh: '暂无运行记录', en: 'No run records' },
|
||||
{ zh: '自动导入', en: 'Auto-imported' },
|
||||
{ zh: '自定义', en: 'Custom' },
|
||||
{ zh: '从列表移除', en: 'Remove' },
|
||||
{ zh: '消息', en: 'Message' },
|
||||
{ zh: '处理统计', en: 'Processing Statistics' },
|
||||
{ zh: '近期运行结果', en: 'Recent Run Results' },
|
||||
{ zh: '平均耗时 (秒)', en: 'Avg Duration (s)' },
|
||||
{ zh: '平均耗时 (s)', en: 'Avg Duration (s)' },
|
||||
{ zh: '结果入库趋势', en: 'Result Import Trend' },
|
||||
{ zh: '入库数量', en: 'Imported Count' },
|
||||
{ zh: '其他', en: 'Other' },
|
||||
{ zh: '就绪', en: 'Ready' },
|
||||
{ zh: '未就绪', en: 'Not Ready' },
|
||||
{ zh: '状态', en: 'Status' },
|
||||
{ zh: '查看日志', en: 'View Log' },
|
||||
{ zh: '文件大小', en: 'File Size' },
|
||||
{ zh: '已截断,仅显示末尾', en: 'Truncated, showing last' },
|
||||
|
||||
// --- IDLAutomationPanel: dynamic messages ---
|
||||
{ zh: '执行失败', en: 'Execution failed' },
|
||||
{ zh: '未知错误', en: 'Unknown error' },
|
||||
{ zh: '只读账号无法提交任务。', en: 'Read-only account cannot submit tasks.' },
|
||||
{ zh: '请填写数据目录。', en: 'Please enter data directory.' },
|
||||
{ zh: '只读账号无法执行提取。', en: 'Read-only account cannot run extraction.' },
|
||||
{ zh: '已自动触发结果扫描入库', en: 'Auto-triggered result scan' },
|
||||
{ zh: '扫描入库触发失败', en: 'Result scan trigger failed' },
|
||||
{ zh: '提取完成', en: 'Extraction complete' },
|
||||
{ zh: '总览加载失败', en: 'Overview load failed' },
|
||||
{ zh: '任务已强制取消,前端已解锁。', en: 'Task force-cancelled, UI unlocked.' },
|
||||
{ zh: '强制解锁失败', en: 'Force unlock failed' },
|
||||
{ zh: '任务已入队', en: 'Task queued' },
|
||||
|
||||
// --- HazardPointPanel ---
|
||||
{ zh: '地质灾害点监测', en: 'Geological Hazard Monitoring' },
|
||||
{ zh: '隐藏全部', en: 'Hide All' },
|
||||
{ zh: '显示全部', en: 'Show All' },
|
||||
{ zh: '同步中...', en: 'Syncing...' },
|
||||
{ zh: '同步 Shapefile 数据', en: 'Sync Shapefile' },
|
||||
{ zh: '搜索编号、名称、市县...', en: 'Search ID, name, city...' },
|
||||
{ zh: '未找到匹配的灾害点', en: 'No matching hazard points' },
|
||||
{ zh: '获取灾害点失败', en: 'Failed to load hazard points' },
|
||||
{ zh: '当前账号为只读模式,无法同步灾害点。', en: 'Read-only account cannot sync hazard points.' },
|
||||
{ zh: '正在发起灾害点同步任务...', en: 'Starting hazard point sync...' },
|
||||
{ zh: '正在同步灾害点数据...', en: 'Syncing hazard point data...' },
|
||||
{ zh: '同步失败', en: 'Sync failed' },
|
||||
|
||||
// --- DataMonitorPanel ---
|
||||
{ zh: '数据监控面板', en: 'Data Monitor' },
|
||||
{ zh: '路径摘要', en: 'Path Summary' },
|
||||
{ zh: '未配置', en: 'Not configured' },
|
||||
{ zh: '监控模式', en: 'Monitor Mode' },
|
||||
{ zh: '手动', en: 'Manual' },
|
||||
{ zh: '配置', en: 'Config' },
|
||||
{ zh: '归档解包', en: 'Archive Unpack' },
|
||||
{ zh: '来源目录', en: 'Source Dir' },
|
||||
{ zh: '轨道', en: 'Orbit' },
|
||||
{ zh: '活动任务', en: 'Active Tasks' },
|
||||
{ zh: '当前无活动任务。', en: 'No active tasks.' },
|
||||
{ zh: '扫描雷达数据', en: 'Scan Radar Data' },
|
||||
{ zh: '扫描精轨数据', en: 'Scan Orbit Data' },
|
||||
{ zh: '扫描D-InSAR结果', en: 'Scan D-InSAR Results' },
|
||||
{ zh: '实时日志', en: 'Live Logs' },
|
||||
{ zh: '暂无日志...', en: 'No logs...' },
|
||||
{ zh: '运行中...', en: 'Running...' },
|
||||
{ zh: '运行解包', en: 'Run Unpack' },
|
||||
{ zh: '只读模式', en: 'Read-only' },
|
||||
{ zh: '当前账号为只读模式,无法触发扫描。', en: 'Read-only account cannot trigger scans.' },
|
||||
{ zh: '当前账号为只读模式,无法触发解包任务。', en: 'Read-only account cannot trigger unpack.' },
|
||||
{ zh: '解包任务启动中...', en: 'Starting unpack task...' },
|
||||
{ zh: '解包任务已启动', en: 'Unpack task started' },
|
||||
{ zh: '雷达数据', en: 'Radar data' },
|
||||
{ zh: '精轨数据', en: 'Orbit data' },
|
||||
{ zh: 'D-InSAR结果', en: 'D-InSAR results' },
|
||||
{ zh: '仅手动模式。路径从 .env 读取;如需修改请更新 .env 并重启后端。', en: 'Manual mode. Paths loaded from .env; update .env and restart backend to modify.' },
|
||||
{ zh: '未加载到监控状态,请检查后端 /api/monitor/status。', en: 'Monitor status not loaded. Check backend /api/monitor/status.' },
|
||||
{ zh: '触发失败', en: 'Trigger failed' },
|
||||
|
||||
// --- DataCopierPanel ---
|
||||
{ zh: '已完成', en: 'Completed' },
|
||||
{ zh: 'PS 分发', en: 'PS Distribution' },
|
||||
{ zh: 'D-InSAR 分发', en: 'D-InSAR Distribution' },
|
||||
{ zh: '当前账号为只读模式,无法发起复制任务。', en: 'Read-only account cannot run copy tasks.' },
|
||||
{ zh: '当前账号为只读模式,无法执行复制任务。', en: 'Read-only account cannot run copy tasks.' },
|
||||
{ zh: '请选择批次并设置目标目录。', en: 'Select batch and set target directory.' },
|
||||
{ zh: '请至少选择一个任务状态。', en: 'Select at least one task status.' },
|
||||
{ zh: '启动失败', en: 'Start failed' },
|
||||
{ zh: '选择批次', en: 'Select Batch' },
|
||||
{ zh: '-- 请选择 --', en: '-- Select --' },
|
||||
{ zh: '任务状态筛选', en: 'Task Status Filter' },
|
||||
{ zh: '目标目录', en: 'Target Directory' },
|
||||
{ zh: '复制中...', en: 'Copying...' },
|
||||
{ zh: '开始复制', en: 'Start Copy' },
|
||||
{ zh: '等待任务...', en: 'Waiting for task...' },
|
||||
{ zh: '默认仅复制"已完成"项。', en: 'Only "Completed" items are copied by default.' },
|
||||
|
||||
// --- WaterMonitorPanel (status labels) ---
|
||||
{ zh: '等待中', en: 'Pending' },
|
||||
{ zh: '完成', en: 'Done' },
|
||||
{ zh: '共', en: 'Total' },
|
||||
|
||||
// --- App.jsx: log messages ---
|
||||
{ zh: '搜索全部源数据', en: 'Search All' },
|
||||
];
|
||||
|
||||
|
||||
const ZH_TO_EN = [...TRANSLATION_PAIRS].sort((a, b) => b.zh.length - a.zh.length);
|
||||
const EN_TO_ZH = [...TRANSLATION_PAIRS].sort((a, b) => b.en.length - a.en.length);
|
||||
|
||||
const EXCEPTION_HINT_PATTERNS = [
|
||||
/\btraceback\b/i,
|
||||
/\b(?:[A-Za-z_]\w*)(?:Error|Exception)\b/,
|
||||
/\bNoneType\b/,
|
||||
/\bobject is not callable\b/i,
|
||||
/File "[^"]+", line \d+/,
|
||||
/\bat\s+\S+\s+\([^)]*:\d+:\d+\)/,
|
||||
/\bASGI\b/,
|
||||
/\buvicorn\b/i,
|
||||
/\bstatus_code\b/i,
|
||||
/\bSQLSTATE\b/i,
|
||||
];
|
||||
|
||||
const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
const shouldBypassTranslation = (text) => EXCEPTION_HINT_PATTERNS.some((pattern) => pattern.test(text));
|
||||
|
||||
const replaceAllPairs = (text, pairs, fromKey, toKey) => {
|
||||
let output = text;
|
||||
pairs.forEach((item) => {
|
||||
const source = item[fromKey];
|
||||
const target = item[toKey];
|
||||
if (!source) return;
|
||||
output = output.replace(new RegExp(escapeForRegExp(source), 'g'), target);
|
||||
});
|
||||
return output;
|
||||
};
|
||||
|
||||
export const translateText = (text, language) => {
|
||||
if (typeof text !== 'string' || !text) return text;
|
||||
if (shouldBypassTranslation(text)) return text;
|
||||
if (language === 'en') {
|
||||
return replaceAllPairs(text, ZH_TO_EN, 'zh', 'en');
|
||||
}
|
||||
return replaceAllPairs(text, EN_TO_ZH, 'en', 'zh');
|
||||
};
|
||||
|
||||
export const SUPPORTED_LANGUAGES = ['zh', 'en'];
|
||||
Reference in New Issue
Block a user