mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-23 22:48:57 -05:00
feat:app remaining text supports 18n && enable translate (#148)
* feat:app remaining text supports 18n && enable translate * refactor:remove unused canI18n code * feat:save user language settings Thanks to @AmazingRain !
This commit is contained in:
@@ -1,11 +1,58 @@
|
||||
{
|
||||
"nav": {
|
||||
"newDiagram": "New Diagram",
|
||||
"saveSessionOnly": "Save (Session Only)",
|
||||
"loadSessionOnly": "Load (Session Only)",
|
||||
"importFile": "Import File",
|
||||
"exportFile": "Export File",
|
||||
"quickSaveSession": "Quick Save(Session)",
|
||||
"serverStorage": "Server Storage"
|
||||
}
|
||||
"nav": {
|
||||
"newDiagram": "New Diagram",
|
||||
"saveSessionOnly": "Save (Session Only)",
|
||||
"loadSessionOnly": "Load (Session Only)",
|
||||
"importFile": "Import File",
|
||||
"exportFile": "Export File",
|
||||
"quickSaveSession": "Quick Save (Session)",
|
||||
"serverStorage": "Server Storage"
|
||||
},
|
||||
"status": {
|
||||
"current": "Current",
|
||||
"untitled": "Untitled Diagram",
|
||||
"modified": "Modified",
|
||||
"sessionStorageNote": "Session storage only - export to save permanently"
|
||||
},
|
||||
"dialog": {
|
||||
"save": {
|
||||
"title": "Save Diagram (Current Session Only)",
|
||||
"warningTitle": "Important",
|
||||
"warningMessage": "This save is temporary and will be lost when you close the browser.",
|
||||
"warningExport": "Use <strong>Export File</strong> to permanently save your work.",
|
||||
"placeholder": "Enter diagram name",
|
||||
"btnSave": "Save",
|
||||
"btnCancel": "Cancel"
|
||||
},
|
||||
"load": {
|
||||
"title": "Load Diagram (Current Session Only)",
|
||||
"noteTitle": "Note",
|
||||
"noteMessage": "These saves are temporary. Export your diagrams to keep them permanently.",
|
||||
"noSavedDiagrams": "No saved diagrams found in this session",
|
||||
"updated": "Updated",
|
||||
"btnLoad": "Load",
|
||||
"btnDelete": "Delete",
|
||||
"btnClose": "Close"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export Diagram",
|
||||
"recommendedTitle": "Recommended",
|
||||
"recommendedMessage": "This is the best way to save your work permanently.",
|
||||
"noteMessage": "Exported JSON files can be imported later or shared with others.",
|
||||
"btnDownload": "Download JSON",
|
||||
"btnCancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"enterDiagramName": "Please enter a diagram name",
|
||||
"diagramExists": "A diagram named \"{{name}}\" already exists in this session. This will overwrite it. Are you sure you want to continue?",
|
||||
"unsavedChanges": "You have unsaved changes. Continue loading?",
|
||||
"createNewDiagram": "Create a new diagram?",
|
||||
"unsavedChangesExport": "You have unsaved changes. Export your diagram first to save it. Continue?",
|
||||
"confirmDelete": "Are you sure you want to delete this diagram?",
|
||||
"storageFull": "Storage full! Opening Storage Manager...",
|
||||
"autoSaveFailed": "Storage full! Please use Storage Manager to free up space.",
|
||||
"beforeUnload": "You have unsaved changes. Are you sure you want to leave?",
|
||||
"quotaExceeded": "Storage quota exceeded. Please export important diagrams and clear some space."
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,58 @@
|
||||
{
|
||||
"nav": {
|
||||
"newDiagram": "新建图表",
|
||||
"saveSessionOnly": "保存(仅会话)",
|
||||
"loadSessionOnly": "加载(仅会话)",
|
||||
"importFile": "导入文件",
|
||||
"exportFile": "导出文件",
|
||||
"quickSaveSession": "快速保存(会话)",
|
||||
"serverStorage": "服务端存储"
|
||||
}
|
||||
"nav": {
|
||||
"newDiagram": "新建图表",
|
||||
"saveSessionOnly": "保存(仅会话)",
|
||||
"loadSessionOnly": "加载(仅会话)",
|
||||
"importFile": "导入文件",
|
||||
"exportFile": "导出文件",
|
||||
"quickSaveSession": "快速保存(会话)",
|
||||
"serverStorage": "服务端存储"
|
||||
},
|
||||
"status": {
|
||||
"current": "当前",
|
||||
"untitled": "未命名图表",
|
||||
"modified": "已修改",
|
||||
"sessionStorageNote": "仅会话存储 - 导出以永久保存"
|
||||
},
|
||||
"dialog": {
|
||||
"save": {
|
||||
"title": "保存图表(仅当前会话)",
|
||||
"warningTitle": "重要提示",
|
||||
"warningMessage": "此保存是临时的,关闭浏览器后将丢失。",
|
||||
"warningExport": "使用<strong>导出文件</strong>功能永久保存您的工作。",
|
||||
"placeholder": "输入图表名称",
|
||||
"btnSave": "保存",
|
||||
"btnCancel": "取消"
|
||||
},
|
||||
"load": {
|
||||
"title": "加载图表(仅当前会话)",
|
||||
"noteTitle": "提示",
|
||||
"noteMessage": "这些保存是临时的。导出您的图表以永久保存。",
|
||||
"noSavedDiagrams": "当前会话中未找到已保存的图表",
|
||||
"updated": "更新时间",
|
||||
"btnLoad": "加载",
|
||||
"btnDelete": "删除",
|
||||
"btnClose": "关闭"
|
||||
},
|
||||
"export": {
|
||||
"title": "导出图表",
|
||||
"recommendedTitle": "推荐",
|
||||
"recommendedMessage": "这是永久保存工作的最佳方式。",
|
||||
"noteMessage": "导出的 JSON 文件可以稍后导入或与他人共享。",
|
||||
"btnDownload": "下载 JSON",
|
||||
"btnCancel": "取消"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"enterDiagramName": "请输入图表名称",
|
||||
"diagramExists": "名为\"{{name}}\"的图表已存在于此会话中。这将覆盖它。您确定要继续吗?",
|
||||
"unsavedChanges": "您有未保存的更改。继续加载?",
|
||||
"createNewDiagram": "创建新图表?",
|
||||
"unsavedChangesExport": "您有未保存的更改。请先导出图表以保存。继续?",
|
||||
"confirmDelete": "您确定要删除此图表吗?",
|
||||
"storageFull": "存储空间已满!正在打开存储管理器...",
|
||||
"autoSaveFailed": "存储空间已满!请使用存储管理器释放空间。",
|
||||
"beforeUnload": "您有未保存的更改。您确定要离开吗?",
|
||||
"quotaExceeded": "存储配额已超出。请导出重要图表并清理一些空间。"
|
||||
}
|
||||
}
|
||||
@@ -138,14 +138,14 @@ function App() {
|
||||
} catch (e) {
|
||||
console.error('Failed to save diagrams:', e);
|
||||
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
|
||||
alert('Storage quota exceeded. Please export important diagrams and clear some space.');
|
||||
alert(t('alert.quotaExceeded'));
|
||||
}
|
||||
}
|
||||
}, [diagrams]);
|
||||
|
||||
const saveDiagram = () => {
|
||||
if (!diagramName.trim()) {
|
||||
alert('Please enter a diagram name');
|
||||
alert(t('alert.enterDiagramName'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ function App() {
|
||||
|
||||
if (existingDiagram) {
|
||||
const confirmOverwrite = window.confirm(
|
||||
`A diagram named "${diagramName}" already exists in this session. This will overwrite it. Are you sure you want to continue?`
|
||||
t('alert.diagramExists', { name: diagramName })
|
||||
);
|
||||
if (!confirmOverwrite) {
|
||||
return;
|
||||
@@ -210,14 +210,14 @@ function App() {
|
||||
} catch (e) {
|
||||
console.error('Failed to save diagram:', e);
|
||||
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
|
||||
alert('Storage full! Opening Storage Manager...');
|
||||
alert(t('alert.storageFull'));
|
||||
setShowStorageManager(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadDiagram = (diagram: SavedDiagram) => {
|
||||
if (hasUnsavedChanges && !window.confirm('You have unsaved changes. Continue loading?')) {
|
||||
if (hasUnsavedChanges && !window.confirm(t('alert.unsavedChanges'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ function App() {
|
||||
};
|
||||
|
||||
const deleteDiagram = (id: string) => {
|
||||
if (window.confirm('Are you sure you want to delete this diagram?')) {
|
||||
if (window.confirm(t('alert.confirmDelete'))) {
|
||||
setDiagrams(diagrams.filter(d => d.id !== id));
|
||||
if (currentDiagram?.id === id) {
|
||||
setCurrentDiagram(null);
|
||||
@@ -258,8 +258,8 @@ function App() {
|
||||
|
||||
const newDiagram = () => {
|
||||
const message = hasUnsavedChanges
|
||||
? 'You have unsaved changes. Export your diagram first to save it. Continue?'
|
||||
: 'Create a new diagram?';
|
||||
? t('alert.unsavedChangesExport')
|
||||
: t('alert.createNewDiagram');
|
||||
|
||||
if (window.confirm(message)) {
|
||||
const emptyDiagram: DiagramData = {
|
||||
@@ -400,14 +400,7 @@ function App() {
|
||||
};
|
||||
|
||||
// i18n
|
||||
const [canI18n, setCanI18n] = useState(false);
|
||||
const { t, i18n } = useTranslation('app');
|
||||
useEffect(() => {
|
||||
// http://localhost:3000/?canI18n=1
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
// show demo
|
||||
setCanI18n(params.get('canI18n') === '1');
|
||||
}, [window.location.search]);
|
||||
|
||||
// Auto-save functionality
|
||||
useEffect(() => {
|
||||
@@ -445,7 +438,7 @@ function App() {
|
||||
} catch (e) {
|
||||
console.error('Auto-save failed:', e);
|
||||
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
|
||||
alert('Storage full! Please use Storage Manager to free up space.');
|
||||
alert(t('alert.autoSaveFailed'));
|
||||
setShowStorageManager(true);
|
||||
}
|
||||
}
|
||||
@@ -459,7 +452,7 @@ function App() {
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (hasUnsavedChanges) {
|
||||
e.preventDefault();
|
||||
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
|
||||
e.returnValue = t('alert.beforeUnload');
|
||||
return e.returnValue;
|
||||
}
|
||||
};
|
||||
@@ -471,7 +464,6 @@ function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="toolbar">
|
||||
{canI18n && <ChangeLanguage />}
|
||||
<button onClick={newDiagram}>{t('nav.newDiagram')}</button>
|
||||
{serverStorageAvailable && (
|
||||
<button
|
||||
@@ -505,11 +497,12 @@ function App() {
|
||||
>
|
||||
{t('nav.quickSaveSession')}
|
||||
</button>
|
||||
<ChangeLanguage />
|
||||
<span className="current-diagram">
|
||||
{currentDiagram ? `Current: ${currentDiagram.name}` : diagramName || 'Untitled Diagram'}
|
||||
{hasUnsavedChanges && <span style={{ color: '#ff9800', marginLeft: '10px' }}>• Modified</span>}
|
||||
{currentDiagram ? `${t('status.current')}: ${currentDiagram.name}` : diagramName || t('status.untitled')}
|
||||
{hasUnsavedChanges && <span style={{ color: '#ff9800', marginLeft: '10px' }}>• {t('status.modified')}</span>}
|
||||
<span style={{ fontSize: '12px', color: '#666', marginLeft: '10px' }}>
|
||||
(Session storage only - export to save permanently)
|
||||
({t('status.sessionStorageNote')})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -528,7 +521,7 @@ function App() {
|
||||
{showSaveDialog && (
|
||||
<div className="dialog-overlay">
|
||||
<div className="dialog">
|
||||
<h2>Save Diagram (Current Session Only)</h2>
|
||||
<h2>{t('dialog.save.title')}</h2>
|
||||
<div style={{
|
||||
backgroundColor: '#fff3cd',
|
||||
border: '1px solid #ffeeba',
|
||||
@@ -536,21 +529,21 @@ function App() {
|
||||
borderRadius: '4px',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<strong>⚠️ Important:</strong> This save is temporary and will be lost when you close the browser.
|
||||
<strong>⚠️ {t('dialog.save.warningTitle')}:</strong> {t('dialog.save.warningMessage')}
|
||||
<br />
|
||||
Use <strong>Export File</strong> to permanently save your work.
|
||||
<span dangerouslySetInnerHTML={{ __html: t('dialog.save.warningExport') }} />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter diagram name"
|
||||
placeholder={t('dialog.save.placeholder')}
|
||||
value={diagramName}
|
||||
onChange={(e) => setDiagramName(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && saveDiagram()}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="dialog-buttons">
|
||||
<button onClick={saveDiagram}>Save</button>
|
||||
<button onClick={() => setShowSaveDialog(false)}>Cancel</button>
|
||||
<button onClick={saveDiagram}>{t('dialog.save.btnSave')}</button>
|
||||
<button onClick={() => setShowSaveDialog(false)}>{t('dialog.save.btnCancel')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -560,7 +553,7 @@ function App() {
|
||||
{showLoadDialog && (
|
||||
<div className="dialog-overlay">
|
||||
<div className="dialog">
|
||||
<h2>Load Diagram (Current Session Only)</h2>
|
||||
<h2>{t('dialog.load.title')}</h2>
|
||||
<div style={{
|
||||
backgroundColor: '#fff3cd',
|
||||
border: '1px solid #ffeeba',
|
||||
@@ -568,29 +561,29 @@ function App() {
|
||||
borderRadius: '4px',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<strong>⚠️ Note:</strong> These saves are temporary. Export your diagrams to keep them permanently.
|
||||
<strong>⚠️ {t('dialog.load.noteTitle')}:</strong> {t('dialog.load.noteMessage')}
|
||||
</div>
|
||||
<div className="diagram-list">
|
||||
{diagrams.length === 0 ? (
|
||||
<p>No saved diagrams found in this session</p>
|
||||
<p>{t('dialog.load.noSavedDiagrams')}</p>
|
||||
) : (
|
||||
diagrams.map(diagram => (
|
||||
<div key={diagram.id} className="diagram-item">
|
||||
<div>
|
||||
<strong>{diagram.name}</strong>
|
||||
<br />
|
||||
<small>Updated: {new Date(diagram.updatedAt).toLocaleString()}</small>
|
||||
<small>{t('dialog.load.updated')}: {new Date(diagram.updatedAt).toLocaleString()}</small>
|
||||
</div>
|
||||
<div className="diagram-actions">
|
||||
<button onClick={() => loadDiagram(diagram)}>Load</button>
|
||||
<button onClick={() => deleteDiagram(diagram.id)}>Delete</button>
|
||||
<button onClick={() => loadDiagram(diagram)}>{t('dialog.load.btnLoad')}</button>
|
||||
<button onClick={() => deleteDiagram(diagram.id)}>{t('dialog.load.btnDelete')}</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
<button onClick={() => setShowLoadDialog(false)}>Close</button>
|
||||
<button onClick={() => setShowLoadDialog(false)}>{t('dialog.load.btnClose')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -601,7 +594,7 @@ function App() {
|
||||
{showExportDialog && (
|
||||
<div className="dialog-overlay">
|
||||
<div className="dialog">
|
||||
<h2>Export Diagram</h2>
|
||||
<h2>{t('dialog.export.title')}</h2>
|
||||
<div style={{
|
||||
backgroundColor: '#d4edda',
|
||||
border: '1px solid #c3e6cb',
|
||||
@@ -610,15 +603,15 @@ function App() {
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<p style={{ margin: '0 0 10px 0' }}>
|
||||
<strong>✅ Recommended:</strong> This is the best way to save your work permanently.
|
||||
<strong>✅ {t('dialog.export.recommendedTitle')}:</strong> {t('dialog.export.recommendedMessage')}
|
||||
</p>
|
||||
<p style={{ margin: 0, fontSize: '14px', color: '#155724' }}>
|
||||
Exported JSON files can be imported later or shared with others.
|
||||
{t('dialog.export.noteMessage')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
<button onClick={exportDiagram}>Download JSON</button>
|
||||
<button onClick={() => setShowExportDialog(false)}>Cancel</button>
|
||||
<button onClick={exportDiagram}>{t('dialog.export.btnDownload')}</button>
|
||||
<button onClick={() => setShowExportDialog(false)}>{t('dialog.export.btnCancel')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,54 +4,55 @@ import './styles.css';
|
||||
import { supportedLanguages } from '../../i18n';
|
||||
|
||||
const ChangeLanguage = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [currentLang, setCurrentLang] = useState(i18n.language || 'en');
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const { i18n } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [currentLang, setCurrentLang] = useState(i18n.language || 'en-US');
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const changeLanguage = (lang: string) => {
|
||||
i18n.changeLanguage(lang);
|
||||
setCurrentLang(lang);
|
||||
const changeLanguage = (lang: string) => {
|
||||
i18n.changeLanguage(lang);
|
||||
setCurrentLang(lang);
|
||||
setIsOpen(false);
|
||||
localStorage.setItem('i18nextLng', lang);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="language-selector" ref={dropdownRef}>
|
||||
<div
|
||||
className="language-display"
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
return (
|
||||
<div className="language-selector" ref={dropdownRef}>
|
||||
<div
|
||||
className="language-display"
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
>
|
||||
A/文
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="language-dropdown">
|
||||
{supportedLanguages.map(item => (
|
||||
<div
|
||||
key={item.value}
|
||||
className={`language-option ${currentLang === item.value ? 'active' : ''}`}
|
||||
onClick={() => changeLanguage(item.value)}
|
||||
>
|
||||
A/文
|
||||
{item.label}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="language-dropdown">
|
||||
{supportedLanguages.map(item => (
|
||||
<div
|
||||
key={item.value}
|
||||
className={`language-option ${currentLang === item.value ? 'active' : ''}`}
|
||||
onClick={() => changeLanguage(item.value)}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeLanguage;
|
||||
export default ChangeLanguage;
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
.language-selector {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-display {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.language-option {
|
||||
padding: 8px 12px;
|
||||
transition: background-color 0.2s;
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
transition: background-color 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.language-option:hover {
|
||||
background-color: #f0f0f0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.language-option.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
@@ -4,35 +4,34 @@ import Backend from 'i18next-http-backend';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: 'en-US',
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
ns: ['app'],
|
||||
backend: {
|
||||
loadPath: './i18n/{{ns}}/{{lng}}.json'
|
||||
},
|
||||
detection: {
|
||||
// configure detection options
|
||||
order: ['navigator', 'htmlTag', 'querystring', 'cookie', 'localStorage'],
|
||||
caches: ['localStorage', 'cookie'],
|
||||
}
|
||||
});
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: 'en-US',
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
ns: ['app'],
|
||||
backend: {
|
||||
loadPath: './i18n/{{ns}}/{{lng}}.json'
|
||||
},
|
||||
detection: {
|
||||
order: ['localStorage'],
|
||||
caches: ['localStorage'],
|
||||
}
|
||||
});
|
||||
|
||||
export const supportedLanguages = [
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
label: '中文',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en-US',
|
||||
},
|
||||
{
|
||||
label: '中文',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
];
|
||||
|
||||
export default i18n;
|
||||
export default i18n;
|
||||
|
||||
@@ -1,11 +1,58 @@
|
||||
{
|
||||
"nav": {
|
||||
"newDiagram": "New Diagram",
|
||||
"saveSessionOnly": "Save (Session Only)",
|
||||
"loadSessionOnly": "Load (Session Only)",
|
||||
"importFile": "Import File",
|
||||
"exportFile": "Export File",
|
||||
"quickSaveSession": "Quick Save(Session)",
|
||||
"serverStorage": "Server Storage"
|
||||
}
|
||||
"nav": {
|
||||
"newDiagram": "New Diagram",
|
||||
"saveSessionOnly": "Save (Session Only)",
|
||||
"loadSessionOnly": "Load (Session Only)",
|
||||
"importFile": "Import File",
|
||||
"exportFile": "Export File",
|
||||
"quickSaveSession": "Quick Save (Session)",
|
||||
"serverStorage": "Server Storage"
|
||||
},
|
||||
"status": {
|
||||
"current": "Current",
|
||||
"untitled": "Untitled Diagram",
|
||||
"modified": "Modified",
|
||||
"sessionStorageNote": "Session storage only - export to save permanently"
|
||||
},
|
||||
"dialog": {
|
||||
"save": {
|
||||
"title": "Save Diagram (Current Session Only)",
|
||||
"warningTitle": "Important",
|
||||
"warningMessage": "This save is temporary and will be lost when you close the browser.",
|
||||
"warningExport": "Use <strong>Export File</strong> to permanently save your work.",
|
||||
"placeholder": "Enter diagram name",
|
||||
"btnSave": "Save",
|
||||
"btnCancel": "Cancel"
|
||||
},
|
||||
"load": {
|
||||
"title": "Load Diagram (Current Session Only)",
|
||||
"noteTitle": "Note",
|
||||
"noteMessage": "These saves are temporary. Export your diagrams to keep them permanently.",
|
||||
"noSavedDiagrams": "No saved diagrams found in this session",
|
||||
"updated": "Updated",
|
||||
"btnLoad": "Load",
|
||||
"btnDelete": "Delete",
|
||||
"btnClose": "Close"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export Diagram",
|
||||
"recommendedTitle": "Recommended",
|
||||
"recommendedMessage": "This is the best way to save your work permanently.",
|
||||
"noteMessage": "Exported JSON files can be imported later or shared with others.",
|
||||
"btnDownload": "Download JSON",
|
||||
"btnCancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"enterDiagramName": "Please enter a diagram name",
|
||||
"diagramExists": "A diagram named \"{{name}}\" already exists in this session. This will overwrite it. Are you sure you want to continue?",
|
||||
"unsavedChanges": "You have unsaved changes. Continue loading?",
|
||||
"createNewDiagram": "Create a new diagram?",
|
||||
"unsavedChangesExport": "You have unsaved changes. Export your diagram first to save it. Continue?",
|
||||
"confirmDelete": "Are you sure you want to delete this diagram?",
|
||||
"storageFull": "Storage full! Opening Storage Manager...",
|
||||
"autoSaveFailed": "Storage full! Please use Storage Manager to free up space.",
|
||||
"beforeUnload": "You have unsaved changes. Are you sure you want to leave?",
|
||||
"quotaExceeded": "Storage quota exceeded. Please export important diagrams and clear some space."
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,58 @@
|
||||
{
|
||||
"nav": {
|
||||
"newDiagram": "新建图表",
|
||||
"saveSessionOnly": "保存(仅会话)",
|
||||
"loadSessionOnly": "加载(仅会话)",
|
||||
"importFile": "导入文件",
|
||||
"exportFile": "导出文件",
|
||||
"quickSaveSession": "快速保存(会话)",
|
||||
"serverStorage": "服务端存储"
|
||||
}
|
||||
"nav": {
|
||||
"newDiagram": "新建图表",
|
||||
"saveSessionOnly": "保存(仅会话)",
|
||||
"loadSessionOnly": "加载(仅会话)",
|
||||
"importFile": "导入文件",
|
||||
"exportFile": "导出文件",
|
||||
"quickSaveSession": "快速保存(会话)",
|
||||
"serverStorage": "服务端存储"
|
||||
},
|
||||
"status": {
|
||||
"current": "当前",
|
||||
"untitled": "未命名图表",
|
||||
"modified": "已修改",
|
||||
"sessionStorageNote": "仅会话存储 - 导出以永久保存"
|
||||
},
|
||||
"dialog": {
|
||||
"save": {
|
||||
"title": "保存图表(仅当前会话)",
|
||||
"warningTitle": "重要提示",
|
||||
"warningMessage": "此保存是临时的,关闭浏览器后将丢失。",
|
||||
"warningExport": "使用<strong>导出文件</strong>功能永久保存您的工作。",
|
||||
"placeholder": "输入图表名称",
|
||||
"btnSave": "保存",
|
||||
"btnCancel": "取消"
|
||||
},
|
||||
"load": {
|
||||
"title": "加载图表(仅当前会话)",
|
||||
"noteTitle": "提示",
|
||||
"noteMessage": "这些保存是临时的。导出您的图表以永久保存。",
|
||||
"noSavedDiagrams": "当前会话中未找到已保存的图表",
|
||||
"updated": "更新时间",
|
||||
"btnLoad": "加载",
|
||||
"btnDelete": "删除",
|
||||
"btnClose": "关闭"
|
||||
},
|
||||
"export": {
|
||||
"title": "导出图表",
|
||||
"recommendedTitle": "推荐",
|
||||
"recommendedMessage": "这是永久保存工作的最佳方式。",
|
||||
"noteMessage": "导出的 JSON 文件可以稍后导入或与他人共享。",
|
||||
"btnDownload": "下载 JSON",
|
||||
"btnCancel": "取消"
|
||||
}
|
||||
},
|
||||
"alert": {
|
||||
"enterDiagramName": "请输入图表名称",
|
||||
"diagramExists": "名为\"{{name}}\"的图表已存在于此会话中。这将覆盖它。您确定要继续吗?",
|
||||
"unsavedChanges": "您有未保存的更改。继续加载?",
|
||||
"createNewDiagram": "创建新图表?",
|
||||
"unsavedChangesExport": "您有未保存的更改。请先导出图表以保存。继续?",
|
||||
"confirmDelete": "您确定要删除此图表吗?",
|
||||
"storageFull": "存储空间已满!正在打开存储管理器...",
|
||||
"autoSaveFailed": "存储空间已满!请使用存储管理器释放空间。",
|
||||
"beforeUnload": "您有未保存的更改。您确定要离开吗?",
|
||||
"quotaExceeded": "存储配额已超出。请导出重要图表并清理一些空间。"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user