ReMe/reme_studio/app/i18n.ts
jinliyl fc4a5398a8
fix(index): make embedding rebuild explicit and scoped (#508)
* fix(index): make embedding rebuild explicit and scoped

* fix(index): harden scoped reindex completion

* fix(index): guard embedding space transitions

* fix(index): serialize reindex with mutations

* docs(index): clarify scoped reindex semantics

* docs(index): explain synchronous checkpoint snapshots

* fix(index): serialize checkpoint publication
2026-08-28 23:25:17 +08:00

332 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { create } from "zustand";
export type Language = "zh" | "en";
const messages = {
zh: {
memoryWorkspace: "记忆工作区",
newAgentChat: "新建 Agent 对话",
newConversation: "新对话",
workspace: "工作区",
daily: "日记",
knowledgeBase: "知识库",
workspaceTab: "工作区",
chatTab: "对话",
dailyTab: "日记",
knowledgeTab: "知识库",
memoryGraph: "记忆图谱",
memoryGraphShort: "图谱",
memoryGraphLoading: "正在加载记忆图谱…",
memoryGraphLoadFailed: "记忆图谱加载失败",
memoryGraphEmpty: "这个分类还没有已索引的记忆链接",
memoryGraphCounts: "{nodes} 个节点 · {edges} 条链接",
memoryGraphZoomOut: "缩小",
memoryGraphZoomIn: "放大",
memoryGraphFit: "适应画布",
memoryGraphIndexed: "已索引文件",
memoryGraphDirection: "Wiki Link 方向",
memoryGraphOpenFile: "打开 Markdown",
memoryGraphOutbound: "出链 · {count}",
memoryGraphInbound: "入链 · {count}",
download: "下载",
loadingWorkspace: "读取工作区…",
connectionFailed: "无法连接 ReMe",
emptyWorkspace: "工作区暂无文件",
fileReadFailed: "文件读取失败",
edit: "编辑",
split: "分栏",
preview: "预览",
save: "保存",
saving: "保存中",
saved: "已保存",
saveFailed: "保存失败:{error}",
unknownError: "未知错误",
closeCurrentTab: "关闭当前文件",
closeOtherTabs: "关闭其他文件",
discardUnsavedConfirm: "将丢弃 {count} 个文件中未保存的修改,确定关闭吗?",
workspaceFileLimit:
"仅显示最近的 {limit} 个文件,工作区中可能还有其他文件。",
openingFile: "正在打开 {path}",
chatFailed: "对话失败",
chatTitle: "和你的记忆对话",
chatDescription: "搜索、阅读和整理 ReMe 工作区中的内容。",
promptRecent: "总结最近的记录",
promptTasks: "有哪些事项值得继续?",
promptIdeas: "查找关于 Agent Memory 的想法",
you: "你",
thinking: "思考过程",
dataChunk: "数据",
approvalChunk: "审批",
usageChunk: "模型用量",
unknownChunk: "事件 · {type}",
toolCall: "调用参数",
toolResult: "执行结果",
inputTokens: "输入",
outputTokens: "输出",
streaming: "进行中",
completed: "已完成",
askWorkspace: "询问你的工作区…",
send: "发送",
composerHint: "Enter 发送 · Shift + Enter 换行",
toggleNavigator: "切换文件导航",
resizeNavigator: "调整工作区导航宽度",
welcomeDescription: "从左侧打开 Markdown,或者开始一段 Agent 对话。",
startChat: "开始对话",
localFiles: "文件保留在你的本地工作区",
switchLanguage: "切换语言",
appearance: "外观",
lightTheme: "浅色",
darkTheme: "深色",
systemTheme: "跟随系统",
documentation: "文档资料",
github: "GitHub",
settings: "设置",
settingsTitle: "ReMe 设置",
settingsDescription: "查看运行状态并管理本地工作区服务。",
settingsStatus: "状态",
settingsIndex: "索引",
settingsConfig: "配置",
settingsVersion: "版本",
closeSettings: "关闭设置",
refresh: "刷新",
loadingSettings: "正在读取…",
processMemory: "进程内存",
componentMemory: "组件内存",
componentDetails: "组件明细",
componentDetailsDescription: "组件健康状态与关键运行数据。",
serviceOnline: "服务运行正常",
serviceAttention: "部分组件需要处理",
serviceOverview: "本地记忆服务",
healthyComponents: "健康组件",
healthy: "健康",
unhealthy: "异常",
defaultInstance: "默认实例",
embeddingStore: "向量存储",
fileGraph: "文件图谱",
fileStore: "文件存储",
keywordIndex: "关键词索引",
modelName: "模型",
dimensions: "向量维度",
cacheSize: "缓存条目",
nodes: "节点",
edges: "边",
virtualNodes: "虚拟节点",
pendingNodes: "待处理节点",
chunks: "内容块",
embeddedChunks: "已生成向量",
documents: "文档",
vocabulary: "词汇量",
memoryUsage: "内存占用",
indexTitle: "工作区索引",
indexDescription: "基于已摄取的内容块重建 BM25 和 Embedding 索引。",
rebuildIndex: "重建索引",
rebuildingIndex: "正在重建…",
confirmReindexTitle: "确定重建索引?",
confirmReindexDescription:
"现有 BM25 和 Embedding 索引会基于当前已摄取的内容块重建。不会扫描文件、重新分块或重建图谱。",
cancel: "取消",
confirmReindex: "确认重建",
indexRebuilt: "索引重建完成",
effectiveConfig: "当前生效配置",
redactedConfig: "敏感字段已由 ReMe 后端隐藏。",
currentVersion: "当前版本",
apiEndpoint: "服务地址",
invalidResponse: "ReMe 返回了无法解析的响应(HTTP {status})",
requestFailed: "请求失败(HTTP {status})",
agentUnavailable: "无法连接 Agent(HTTP {status})",
},
en: {
memoryWorkspace: "Memory workspace",
newAgentChat: "New Agent chat",
newConversation: "New chat",
workspace: "Workspace",
daily: "Journal",
knowledgeBase: "Knowledge base",
workspaceTab: "Files",
chatTab: "Chat",
dailyTab: "Daily",
knowledgeTab: "Knowledge",
memoryGraph: "Memory graph",
memoryGraphShort: "Graph",
memoryGraphLoading: "Loading memory graph…",
memoryGraphLoadFailed: "Unable to load memory graph",
memoryGraphEmpty: "No indexed memory links in this category",
memoryGraphCounts: "{nodes} nodes · {edges} links",
memoryGraphZoomOut: "Zoom out",
memoryGraphZoomIn: "Zoom in",
memoryGraphFit: "Fit canvas",
memoryGraphIndexed: "Indexed file",
memoryGraphDirection: "Wiki Link direction",
memoryGraphOpenFile: "Open Markdown",
memoryGraphOutbound: "Outbound · {count}",
memoryGraphInbound: "Inbound · {count}",
download: "Download",
loadingWorkspace: "Loading workspace…",
connectionFailed: "Unable to connect to ReMe",
emptyWorkspace: "No files in this workspace",
fileReadFailed: "Unable to read file",
edit: "Edit",
split: "Split view",
preview: "Preview",
save: "Save",
saving: "Saving",
saved: "Saved",
saveFailed: "Save failed: {error}",
unknownError: "Unknown error",
closeCurrentTab: "Close current file",
closeOtherTabs: "Close other files",
discardUnsavedConfirm:
"Discard unsaved changes in {count} file(s) and close?",
workspaceFileLimit:
"Showing the {limit} most recent files. More files may exist in this workspace.",
openingFile: "Opening {path}",
chatFailed: "Chat failed",
chatTitle: "Chat with your memory",
chatDescription:
"Search, read, and organize content in your ReMe workspace.",
promptRecent: "Summarize my recent notes",
promptTasks: "What should I follow up on?",
promptIdeas: "Find my Agent Memory ideas",
you: "You",
thinking: "Thinking",
dataChunk: "Data",
approvalChunk: "Approval",
usageChunk: "Model usage",
unknownChunk: "Event · {type}",
toolCall: "Call",
toolResult: "Result",
inputTokens: "input",
outputTokens: "output",
streaming: "Streaming",
completed: "Completed",
askWorkspace: "Ask about your workspace…",
send: "Send",
composerHint: "Enter to send · Shift + Enter for a new line",
toggleNavigator: "Toggle file navigator",
resizeNavigator: "Resize workspace navigator",
welcomeDescription:
"Open a Markdown file from the left, or start an Agent conversation.",
startChat: "Start chatting",
localFiles: "Files stay in your local workspace",
switchLanguage: "Switch language",
appearance: "Appearance",
lightTheme: "Light",
darkTheme: "Dark",
systemTheme: "System",
documentation: "Documentation",
github: "GitHub",
settings: "Settings",
settingsTitle: "ReMe Settings",
settingsDescription:
"Inspect runtime status and manage the local workspace service.",
settingsStatus: "Status",
settingsIndex: "Index",
settingsConfig: "Configuration",
settingsVersion: "Version",
closeSettings: "Close settings",
refresh: "Refresh",
loadingSettings: "Loading…",
processMemory: "Process memory",
componentMemory: "Component memory",
componentDetails: "Component details",
componentDetailsDescription: "Component health and key runtime data.",
serviceOnline: "Service is running",
serviceAttention: "Some components need attention",
serviceOverview: "Local memory service",
healthyComponents: "Healthy components",
healthy: "Healthy",
unhealthy: "Needs attention",
defaultInstance: "Default instance",
embeddingStore: "Embedding store",
fileGraph: "File graph",
fileStore: "File store",
keywordIndex: "Keyword index",
modelName: "Model",
dimensions: "Dimensions",
cacheSize: "Cache entries",
nodes: "Nodes",
edges: "Edges",
virtualNodes: "Virtual nodes",
pendingNodes: "Pending nodes",
chunks: "Chunks",
embeddedChunks: "Embedded chunks",
documents: "Documents",
vocabulary: "Vocabulary",
memoryUsage: "Memory",
indexTitle: "Workspace index",
indexDescription:
"Rebuild BM25 and embedding indexes from already-ingested chunks.",
rebuildIndex: "Rebuild index",
rebuildingIndex: "Rebuilding…",
confirmReindexTitle: "Rebuild the index?",
confirmReindexDescription:
"BM25 and embedding indexes will be rebuilt from the currently ingested chunks. Files are not scanned or rechunked, and the graph is not rebuilt.",
cancel: "Cancel",
confirmReindex: "Confirm rebuild",
indexRebuilt: "Index rebuilt",
effectiveConfig: "Effective configuration",
redactedConfig: "Sensitive fields are redacted by the ReMe backend.",
currentVersion: "Current version",
apiEndpoint: "Service endpoint",
invalidResponse: "ReMe returned an invalid response (HTTP {status})",
requestFailed: "Request failed (HTTP {status})",
agentUnavailable: "Unable to connect to Agent (HTTP {status})",
},
} as const;
export type TranslationKey = keyof typeof messages.zh;
export function translate(
language: Language,
key: TranslationKey,
values: Record<string, string> = {},
): string {
let text: string = messages[language][key];
for (const [name, value] of Object.entries(values))
text = text.replace(`{${name}}`, value);
return text;
}
interface LanguageState {
language: Language;
hydrate: () => void;
setLanguage: (language: Language) => void;
}
const applyLanguage = (language: Language) => {
if (typeof document !== "undefined")
document.documentElement.lang = language === "zh" ? "zh-CN" : "en";
};
export const useLanguageStore = create<LanguageState>((set) => ({
language: "zh",
hydrate: () => {
const saved = localStorage.getItem("reme-language");
const language: Language =
saved === "zh" || saved === "en"
? saved
: navigator.language.startsWith("zh")
? "zh"
: "en";
applyLanguage(language);
set({ language });
},
setLanguage: (language) => {
localStorage.setItem("reme-language", language);
applyLanguage(language);
set({ language });
},
}));
export function useI18n() {
const language = useLanguageStore((state) => state.language);
const setLanguage = useLanguageStore((state) => state.setLanguage);
return {
language,
setLanguage,
t: (key: TranslationKey, values?: Record<string, string>) =>
translate(language, key, values),
};
}