-
Appearance
+
+ {t("settings.appearance.title")}
+
- Customize the appearance of the app. Automatically switch between day
- and night themes.
+ {t("settings.appearance.description")}
diff --git a/apps/web-app/settings/layout.tsx b/apps/web-app/settings/layout.tsx
index 760c4dd2..72936853 100644
--- a/apps/web-app/settings/layout.tsx
+++ b/apps/web-app/settings/layout.tsx
@@ -2,6 +2,7 @@
import { useKeyPress } from "ahooks"
import { Minimize2 } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { Outlet } from "react-router-dom"
import { useGoto } from "@/hooks/use-goto"
@@ -14,47 +15,47 @@ import { useLastOpened } from "../[database]/hook"
const sidebarNavItems = [
{
- title: "General",
+ titleKey: "settings.general",
href: "/settings",
},
{
- title: "AI",
+ titleKey: "settings.ai",
href: "/settings/ai",
},
{
- title: "API",
+ titleKey: "settings.api",
href: "/settings/api",
},
{
- title: "Storage",
+ titleKey: "settings.storage",
href: "/settings/storage",
},
- // {
- // title: "Backup(deprecated)",
- // href: "/settings/backup",
- // disabled: true,
- // },
{
- title: "Sync",
+ titleKey: "settings.appearance",
+ href: "/settings/appearance",
+ },
+ {
+ titleKey: "settings.sync",
href: "/settings/sync",
disabled: true,
},
{
- title: "Security",
+ titleKey: "settings.security",
href: "/settings/security",
disabled: true,
},
- {
- title: "Experiment",
- href: "/settings/experiment",
- },
- {
- title: "Devtools",
- href: "/settings/dev",
- },
+ // {
+ // titleKey: "settings.experiment",
+ // href: "/settings/experiment",
+ // },
+ // {
+ // titleKey: "settings.devtools",
+ // href: "/settings/dev",
+ // },
]
export default function SettingsLayout() {
+ const { t } = useTranslation()
const { lastOpenedTable, lastOpenedDatabase } = useLastOpened()
const goto = useGoto()
const goBack = () => goto(lastOpenedDatabase, lastOpenedTable)
@@ -70,19 +71,26 @@ export default function SettingsLayout() {
-
Settings
+
+ {t("settings.title")}
+
- Manage App Settings and Configuration
+ {t("settings.manageAppSettings")}
- ESC
+ {t("common.esc")}
-
+ ({
+ ...item,
+ title: t(item.titleKey),
+ }))}
+ />
diff --git a/apps/web-app/settings/page.tsx b/apps/web-app/settings/page.tsx
index 760eddaa..9a89db2d 100644
--- a/apps/web-app/settings/page.tsx
+++ b/apps/web-app/settings/page.tsx
@@ -1,13 +1,16 @@
+import { useTranslation } from "react-i18next"
import { Separator } from "@/components/ui/separator"
import { ProfileForm } from "@/apps/web-app/settings/profile-form"
export default function SettingsGeneralPage() {
+ const { t } = useTranslation()
+
return (
-
General
+
{t("settings.general")}
- How others will see you when collaborating.
+ {t("settings.general.description")}
diff --git a/apps/web-app/settings/profile-form.tsx b/apps/web-app/settings/profile-form.tsx
index 58ba3691..fb7d9e3c 100644
--- a/apps/web-app/settings/profile-form.tsx
+++ b/apps/web-app/settings/profile-form.tsx
@@ -2,8 +2,15 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { ControllerRenderProps, useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import * as z from "zod"
+import { useActivationCodeStore } from "@/hooks/use-activation"
+import { useEidosFileSystemManager } from "@/hooks/use-fs"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { toast } from "@/components/ui/use-toast"
import {
Form,
FormControl,
@@ -12,13 +19,7 @@ import {
FormLabel,
FormMessage,
} from "@/components/react-hook-form/form"
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { toast } from "@/components/ui/use-toast"
-import { useActivationCodeStore } from "@/hooks/use-activation"
-import { useEidosFileSystemManager } from "@/hooks/use-fs"
import { useConfigStore } from "./store"
const profileFormSchema = z.object({
@@ -51,7 +52,7 @@ export function ProfileForm() {
})
const { clientId } = useActivationCodeStore()
const { efsManager } = useEidosFileSystemManager()
-
+ const { t } = useTranslation()
const handleChangeAvatar = async (
field: ControllerRenderProps<
{
@@ -113,7 +114,7 @@ export function ProfileForm() {
name="username"
render={({ field }) => (
- Name
+ {t("common.name")}
@@ -126,7 +127,9 @@ export function ProfileForm() {
/>
- Client ID
+
+ {t("settings.general.clientId")}
+
@@ -146,7 +149,7 @@ export function ProfileForm() {
)}
/>
-
Update
+
{t("common.update")}
)
diff --git a/apps/web-app/settings/storage/page.tsx b/apps/web-app/settings/storage/page.tsx
index 0020621e..e9003946 100644
--- a/apps/web-app/settings/storage/page.tsx
+++ b/apps/web-app/settings/storage/page.tsx
@@ -1,13 +1,16 @@
import { Separator } from "@/components/ui/separator"
import { StorageForm } from "@/apps/web-app/settings/storage/storage-form"
+import { useTranslation } from 'react-i18next'
export default function SettingsAccountPage() {
+ const { t } = useTranslation()
+
return (
-
Storage
+
{t('settings.storage')}
- Configure your storage settings.
+ {t('settings.storage.description')}
diff --git a/apps/web-app/settings/storage/storage-form.tsx b/apps/web-app/settings/storage/storage-form.tsx
index b6b2450b..f25a99cc 100644
--- a/apps/web-app/settings/storage/storage-form.tsx
+++ b/apps/web-app/settings/storage/storage-form.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { Check, ChevronsUpDown } from "lucide-react"
import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import * as z from "zod"
import { FileSystemType } from "@/lib/storage/eidos-file-system"
@@ -35,9 +36,9 @@ import {
FormMessage,
} from "@/components/react-hook-form/form"
-const fsTypes = [
+const getFsTypes = (t: (key: string) => string) => [
{ label: "OPFS", value: FileSystemType.OPFS },
- { label: "Native File System", value: FileSystemType.NFS },
+ { label: t("settings.storage.nativeFileSystem"), value: FileSystemType.NFS },
] as const
const storageFormSchema = z.object({
@@ -49,6 +50,7 @@ const storageFormSchema = z.object({
type StorageFormValues = z.infer
export function StorageForm() {
+ const { t } = useTranslation()
const [localPath, setLocalPath] =
useIndexedDB("kv", "localPath", null)
const [fsType, setFsType] = useIndexedDB("kv", "fs", FileSystemType.OPFS)
@@ -116,15 +118,15 @@ export function StorageForm() {
if (data.fsType === FileSystemType.NFS) {
if (!localPath) {
toast({
- title: "Local path not selected",
- description: "You need to select a local path to store your files.",
+ title: t("settings.storage.dataFolderNotSelected"),
+ description: t("settings.storage.selectDataFolder"),
})
return
}
if (!isGranted) {
toast({
- title: "Permission denied",
- description: "You need to grant permission to access the directory.",
+ title: t("common.error"),
+ description: t("settings.storage.permissionDenied"),
})
return
}
@@ -135,13 +137,16 @@ export function StorageForm() {
setFsType(data.fsType)
setAutoBackupGap(data.autoBackupGap)
toast({
- title: "Settings updated",
+ title: t("settings.storage.settingsUpdated"),
})
}
// get current fsType
const currentFsType = form.watch("fsType")
+ // Use the function to get fsTypes when needed
+ const fsTypes = getFsTypes(t)
+
return (
diff --git a/components/ai-chat/ai-input-editor/index.tsx b/components/ai-chat/ai-input-editor/index.tsx
index fc92c555..99550f7b 100644
--- a/components/ai-chat/ai-input-editor/index.tsx
+++ b/components/ai-chat/ai-input-editor/index.tsx
@@ -17,6 +17,7 @@ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"
import { HeadingNode, QuoteNode } from "@lexical/rich-text"
import { Message } from "ai/react"
import { $getRoot } from "lexical"
+import { useTranslation } from "react-i18next"
import { BGEM3 } from "@/lib/ai/llm_vendors/bge"
import { embeddingTexts } from "@/lib/embedding/worker"
@@ -83,6 +84,7 @@ export const AIInputEditor = ({
setContextNodes,
setContextEmbeddings,
}: InputEditorProps) => {
+ const { t } = useTranslation()
const initialConfig: InitialConfigType = {
namespace: "AI-Chat-Input-Editor",
theme,
@@ -201,9 +203,10 @@ export const AIInputEditor = ({
}
placeholder={
- Type your message here.
+ {t("aiChat.inputEditor.typeYourMessageHere")}
- Press / to switch prompt. @ to mention resource.
+ {t("aiChat.inputEditor.pressSlashToSwitchPrompt")}
+ {t("aiChat.inputEditor.pressAtToMentionResource")}
}
ErrorBoundary={LexicalErrorBoundary}
diff --git a/components/cmdk/index.tsx b/components/cmdk/index.tsx
index 29562786..11b4dfa7 100644
--- a/components/cmdk/index.tsx
+++ b/components/cmdk/index.tsx
@@ -4,6 +4,7 @@ import { useEffect } from "react"
import { useDebounceFn, useKeyPress } from "ahooks"
import { Bot, Clock3Icon, FilePlus2Icon, Palette, Settings } from "lucide-react"
import { useTheme } from "next-themes"
+import { useTranslation } from "react-i18next"
import { isInkServiceMode } from "@/lib/env"
import { useAppRuntimeStore } from "@/lib/store/runtime-store"
@@ -59,7 +60,8 @@ export function CommandDialogDemo() {
space && run(input)
}, [input, run, space])
- const { isRightPanelOpen: isAiOpen, setIsRightPanelOpen: setIsAiOpen } = useSpaceAppStore()
+ const { isRightPanelOpen: isAiOpen, setIsRightPanelOpen: setIsAiOpen } =
+ useSpaceAppStore()
const { lastOpenedDatabase } = useLastOpened()
const { createDoc } = useSqlite()
@@ -91,60 +93,53 @@ export function CommandDialogDemo() {
return
}
+ const { t } = useTranslation()
+
return (
- not found "{input}"
+ {t("cmdk.notFound", { input })}
{!isInkServiceMode && (
-
+
- Today
+ {t("common.today")}
- {/*
-
- Everyday
- */}
- New Draft Doc
+ {t("cmdk.newDraftDoc")}
- AI
+ {t("common.ai")}
- {/*
-
- Share
- */}
)}
- {/* */}
{!isInkServiceMode && (
<>
>
)}
-
+
- Switch Theme
+ {t("cmdk.switchTheme")}
⌘+Shift+L
{!isInkServiceMode && (
- Settings
+ {t("common.settings")}
)}
diff --git a/components/database-select.tsx b/components/database-select.tsx
index 1bc3b8f7..50b9a4d0 100644
--- a/components/database-select.tsx
+++ b/components/database-select.tsx
@@ -3,6 +3,7 @@
import * as React from "react"
import { kebabCase } from "lodash"
import { Check, ChevronsUpDown, PlusCircle } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { cn } from "@/lib/utils"
import { useCurrentPathInfo } from "@/hooks/use-current-pathinfo"
@@ -42,6 +43,7 @@ interface IDatabaseSelectorProps {
}
export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
+ const { t } = useTranslation()
const [open, setOpen] = React.useState(false)
const [file, setFile] = React.useState(null)
const { spaceList } = useSpace()
@@ -130,7 +132,7 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
aria-expanded={open}
className="w-full min-w-[180px] justify-between"
>
- {space ? {space}
: "Select Database..."}
+ {space ? {space}
: t('space.select.selectDatabase')}
@@ -138,12 +140,12 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
- No database found.
+ {t('common.noResultsFound')}
{databases.map((database) => (
@@ -171,8 +173,8 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
setShowNewTeamDialog(true)
}}
>
- {" "}
- Create New
+
+ {t('space.select.createNew')}
@@ -182,25 +184,24 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
- Create Space
+ {t('space.select.createSpace')}
- Add a new space to manage data for you
+ {t('space.select.createSpaceDescription')}
- Space name
+ {t('space.select.spaceName')}
{
- // disable non-ascii characters, sqlite-wasm handle non-ascii characters incorrectly
if (e.target.value) {
e.target.validity.valid && setDatabaseName(e.target.value)
} else {
@@ -212,7 +213,7 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
{isExistingSpace && !isOverwrite && (
- this space already exists, choose another name
+ {t('space.select.spaceAlreadyExists')}
)}
@@ -220,9 +221,9 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
-
Import from file
+
{t('space.select.importFromFile')}
- if you export space as a zip file, you can import it here
+ {t('space.select.importFromFileDescription')}
{isOverwrite && (
- it seems you are trying to overwrite an existing space. please
- be careful, this will overwrite data in the existing space.
+ {t('space.select.overwriteWarning')}
)}
@@ -242,14 +242,14 @@ export function DatabaseSelect({ databases }: IDatabaseSelectorProps) {
setShowNewTeamDialog(false)}>
- Cancel
+ {t('common.cancel')}
- {loading ? "Creating" : "Continue"}
+ {loading ? t('space.select.creating') : t('common.continue')}
diff --git a/components/doc/editor.tsx b/components/doc/editor.tsx
index e54519f8..d10dbe9a 100644
--- a/components/doc/editor.tsx
+++ b/components/doc/editor.tsx
@@ -8,6 +8,7 @@ import { ContentEditable } from "@lexical/react/LexicalContentEditable"
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"
import { useDebounceFn } from "ahooks"
+import { useTranslation } from "react-i18next"
import { cn } from "@/lib/utils"
import { AIEditorPlugin } from "@/components/doc/plugins/AIEditorPlugin"
@@ -50,6 +51,7 @@ interface EditorProps {
}
export function InnerEditor(props: EditorProps) {
+ const { t } = useTranslation()
const ref = React.useRef
(null)
const { isToolbarVisible, isAIToolsOpen } = useEditorStore()
const [floatingAnchorElem, setFloatingAnchorElem] =
@@ -102,7 +104,7 @@ export function InnerEditor(props: EditorProps) {
}
placeholder={
- {props.placeholder ?? "press / for Command"}
+ {props.placeholder ?? t('doc.pressForCommand')}
}
ErrorBoundary={LexicalErrorBoundary}
@@ -144,6 +146,7 @@ export function InnerEditor(props: EditorProps) {
}
export function Editor(props: EditorProps) {
+ const { t } = useTranslation()
const canChangeTitle = props.onTitleChange !== undefined
const [title, setTitle] = useState(props.title ?? "")
const isLoading = useLoadingExtBlocks()
@@ -186,7 +189,7 @@ export function Editor(props: EditorProps) {
{props.beforeTitle && {props.beforeTitle}
}
(null)
@@ -183,7 +185,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
.map((n: string) => parseInt(n, 10))
options.push(
- new ComponentPickerOption(`${rows}x${columns} Table`, {
+ new ComponentPickerOption(t("doc.menu.insertTable", { rows, columns }), {
icon: ,
keywords: ["table"],
onSelect: () =>
@@ -197,7 +199,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
options.push(
...Array.from({ length: 5 }, (_, i) => i + 1).map(
(columns) =>
- new ComponentPickerOption(`${rows}x${columns} Table`, {
+ new ComponentPickerOption(t("doc.menu.insertTable", { rows, columns }), {
icon: ,
keywords: ["table"],
onSelect: () =>
@@ -209,16 +211,11 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}
return options
- }, [editor, queryString])
+ }, [editor, queryString, t])
const options = useMemo(() => {
const baseOptions = [
- // new ComponentPickerOption("AI Complete", {
- // icon: IconMap["ai"],
- // keywords: ["ai", "auto"],
- // onSelect: () => editor.dispatchCommand(AI_COMPLETE_COMMAND, ""),
- // }),
- new ComponentPickerOption("Paragraph", {
+ new ComponentPickerOption(t("doc.menu.paragraph"), {
icon: IconMap["text"],
keywords: ["normal", "paragraph", "p", "text"],
onSelect: () =>
@@ -231,7 +228,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}),
...Array.from({ length: 3 }, (_, i) => i + 1).map(
(n) =>
- new ComponentPickerOption(`Heading ${n}`, {
+ new ComponentPickerOption(t("doc.menu.heading", { n }), {
icon: IconMap[`h${n}`],
keywords: ["heading", "header", `h${n}`],
onSelect: () =>
@@ -246,25 +243,25 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}),
})
),
- new ComponentPickerOption("Numbered List", {
+ new ComponentPickerOption(t("doc.menu.numberedList"), {
icon: IconMap["lo"],
keywords: ["numbered list", "ordered list", "ol"],
onSelect: () =>
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
}),
- new ComponentPickerOption("Bulleted List", {
+ new ComponentPickerOption(t("doc.menu.bulletedList"), {
icon: IconMap["ul"],
keywords: ["bulleted list", "unordered list", "ul"],
onSelect: () =>
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
}),
- new ComponentPickerOption("Check List", {
+ new ComponentPickerOption(t("doc.menu.checkList"), {
icon: IconMap["cl"],
keywords: ["check list", "todo list"],
onSelect: () =>
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
}),
- new ComponentPickerOption("Quote", {
+ new ComponentPickerOption(t("doc.menu.quote"), {
icon: IconMap["quote"],
keywords: ["block quote"],
onSelect: () =>
@@ -275,7 +272,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}
}),
}),
- new ComponentPickerOption("Code", {
+ new ComponentPickerOption(t("doc.menu.code"), {
icon: IconMap["code"],
keywords: ["javascript", "python", "js", "codeblock"],
onSelect: () =>
@@ -286,7 +283,6 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
if (selection.isCollapsed()) {
$setBlocksType(selection, () => $createCodeNode())
} else {
- // Will this ever happen?
const textContent = selection.getTextContent()
const codeNode = $createCodeNode()
selection.insertNodes([codeNode])
@@ -295,14 +291,13 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}
}),
}),
- new ComponentPickerOption("Divider", {
+ new ComponentPickerOption(t("doc.menu.divider"), {
icon: IconMap["hr"],
keywords: ["horizontal rule", "divider", "hr"],
onSelect: () =>
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
}),
-
- new ComponentPickerOption("Image", {
+ new ComponentPickerOption(t("doc.menu.image"), {
icon: IconMap["image"],
keywords: ["image", "img"],
onSelect: () =>
@@ -311,7 +306,6 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
altText: "",
}),
}),
-
...BuiltInBlocks.map((block) => {
const iconName = block.icon
const BlockIcon = (icons as any)[iconName]
@@ -321,8 +315,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
onSelect: () => block.onSelect(editor),
})
}),
-
- new ComponentPickerOption("Bookmark", {
+ new ComponentPickerOption(t("doc.menu.bookmark"), {
icon: IconMap["bookmark"],
keywords: ["bookmark"],
onSelect: () =>
@@ -330,30 +323,27 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
url: "",
}),
}),
-
- new ComponentPickerOption("Table Of Content", {
+ new ComponentPickerOption(t("doc.menu.tableOfContent"), {
icon: ,
keywords: ["table of content", "toc"],
onSelect: () => editor.dispatchCommand(INSERT_TOC_COMMAND, undefined),
}),
-
- new ComponentPickerOption("Query", {
+ new ComponentPickerOption(t("doc.menu.query"), {
icon: IconMap["sql"],
keywords: ["query", "sql"],
onSelect: () =>
- showModal("Insert SqlQuery", (onClose) => (
+ showModal(t("doc.menu.insertSqlQuery"), (onClose) => (
)),
}),
-
- new ComponentPickerOption("DatabaseTable", {
+ new ComponentPickerOption(t("doc.menu.databaseTable"), {
icon: IconMap["database"],
keywords: ["database", "table"],
disabled: true,
onSelect: () => {
// disable for now
return
- showModal("Insert Database Table", (onClose) => (
+ showModal(t("doc.menu.insertDatabaseTable"), (onClose) => (
- // new ComponentPickerOption(`Align ${alignment}`, {
- // icon: ,
- // keywords: ["align", "justify", alignment],
- // onSelect: () =>
- // // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
- // editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),
- // })
- // ),
...bgColors.map(({ name, value }) => {
- return new ComponentPickerOption(`Background ${name}`, {
+ return new ComponentPickerOption(t("doc.menu.background", { name }), {
icon: (
{
- return new ComponentPickerOption(`Color ${name}`, {
+ return new ComponentPickerOption(t("doc.menu.color", { name }), {
icon: ,
keywords: ["color", name],
onSelect: () =>
@@ -426,17 +405,6 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
onSelect: () => block.onSelect(editor),
})
}),
-
- // ...["left", "center", "right", "justify"].map(
- // (alignment) =>
- // new ComponentPickerOption(`Align ${alignment}`, {
- // icon: ,
- // keywords: ["align", "justify", alignment],
- // onSelect: () =>
- // // @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
- // editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),
- // })
- // ),
]
const dynamicOptions = getDynamicOptions()
@@ -454,7 +422,7 @@ export function ComponentPickerMenuPlugin(): JSX.Element {
}),
]
: baseOptions
- }, [editor, extBlocks, getDynamicOptions, queryString, showModal])
+ }, [editor, extBlocks, getDynamicOptions, queryString, showModal, t])
const onSelectOption = useCallback(
(
diff --git a/components/keyboard-shortcuts/const.ts b/components/keyboard-shortcuts/const.ts
index 2fddf5e7..f52f252c 100644
--- a/components/keyboard-shortcuts/const.ts
+++ b/components/keyboard-shortcuts/const.ts
@@ -1,166 +1,168 @@
-export const TableKeyboardShortcuts = [
- {
- key: "Arrow",
- description:
- "Moves the currently selected cell and clears other selections",
- },
- {
- key: "Shift + Arrow",
- description:
- "Extends the current selection range in the direction pressed.",
- },
- {
- key: "Alt + Arrow",
- description:
- "Moves the currently selected cell and retains the current selection",
- },
- {
- key: "Ctrl/Cmd + Arrow | Home/End",
- description:
- "Move the selection as far as possible in the direction pressed.",
- },
- {
- key: "Ctrl/Cmd + Shift + Arrow",
- description:
- "Extends the selection as far as possible in the direction pressed.",
- },
- {
- key: "Shift + Home/End",
- description:
- "Extends the selection as far as possible in the direction pressed.",
- },
- {
- key: "Ctrl/Cmd + A",
- description: "Selects all cells.",
- },
- {
- key: "Shift + Space",
- description: "Selecs the current row.",
- },
- {
- key: "Ctrl + Space",
- description: "Selects the current col.",
- },
- {
- key: "PageUp/PageDown",
- description: "Moves the current selection up/down by one page.",
- },
- {
- key: "Escape",
- description: "Clear the current selection.",
- },
- {
- key: "Ctrl/Cmd + D",
- description:
- "Data from the first row of the range will be down filled into the rows below it",
- flag: "downFill",
- },
- {
- key: "Ctrl/Cmd + R",
- description:
- "Data from the first column of the range will be right filled into the columns next to it",
- flag: "rightFill",
- },
- {
- key: "Ctrl/Cmd + C",
- description: "Copies the current selection.",
- },
- {
- key: "Ctrl/Cmd + V",
- description: "Pastes the current buffer into the grid.",
- },
- {
- key: "Ctrl/Cmd + F",
- description: "Opens the search interface.(disabled for now)",
- flag: "search",
- disabled: true,
- },
- {
- key: "Ctrl/Cmd + Home/End",
- description: "Move the selection to the first/last cell in the data grid.",
- flag: "first/last",
- },
- {
- key: "Ctrl/Cmd + Shift + Home/End",
- description:
- "Extend the selection to the first/last cell in the data grid.",
- flag: "first/last",
- },
-]
+import { useTranslation } from 'react-i18next';
-/**
- * support most markdown syntax
- */
-export const DocumentKeyboardShortcuts = [
- {
- key: "Ctrl/Cmd + B",
- description: "Bold text",
- },
- {
- key: "Ctrl/Cmd + I",
- description: "Italicize text",
- },
- {
- key: "Ctrl/Cmd + U",
- description: "Underline text",
- },
- {
- key: "Ctrl/Cmd + S",
- description: "Save the document",
- },
- {
- key: "#",
- description: "Heading 1",
- },
- {
- key: "##",
- description: "Heading 2",
- },
- {
- key: "###",
- description: "Heading 3",
- },
- {
- key: "[]",
- description: "Checkbox",
- },
- {
- key: "-",
- description: "Unordered List",
- },
- {
- key: "number + .",
- description: "Ordered List",
- },
- {
- key: "```",
- description: "Code Block",
- },
- {
- key: "---",
- description: "Horizontal Rule",
- },
-]
+export const useTableKeyboardShortcuts = () => {
+ const { t } = useTranslation();
-export const CommonKeyboardShortcuts = [
- {
- key: "Ctrl/Cmd + /",
- description: "Toggle chatbot",
- },
- {
- key: "Ctrl/Cmd + \\",
- description: "Toggle sidebar",
- },
- {
- key: "Ctrl/Cmd + Shift + L",
- description: "Toggle light/dark mode",
- },
- {
- key: "Ctrl/Cmd + K",
- description: "Toggle command palette",
- },
- {
- key: "Ctrl/Cmd + ,",
- description: "Open settings",
- },
-]
+ return [
+ {
+ key: "Arrow",
+ description: t('kbd.shortcuts.table.arrowDescription'),
+ },
+ {
+ key: "Shift + Arrow",
+ description: t('kbd.shortcuts.table.shiftArrowDescription'),
+ },
+ {
+ key: "Alt + Arrow",
+ description: t('kbd.shortcuts.table.altArrowDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + Arrow | Home/End",
+ description: t('kbd.shortcuts.table.ctrlArrowDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + Shift + Arrow",
+ description: t('kbd.shortcuts.table.ctrlShiftArrowDescription'),
+ },
+ {
+ key: "Shift + Home/End",
+ description: t('kbd.shortcuts.table.shiftHomeEndDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + A",
+ description: t('kbd.shortcuts.table.ctrlADescription'),
+ },
+ {
+ key: "Shift + Space",
+ description: t('kbd.shortcuts.table.shiftSpaceDescription'),
+ },
+ {
+ key: "Ctrl + Space",
+ description: t('kbd.shortcuts.table.ctrlSpaceDescription'),
+ },
+ {
+ key: "PageUp/PageDown",
+ description: t('kbd.shortcuts.table.pageUpDownDescription'),
+ },
+ {
+ key: "Escape",
+ description: t('kbd.shortcuts.table.escapeDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + D",
+ description: t('kbd.shortcuts.table.ctrlDDescription'),
+ flag: "downFill",
+ },
+ {
+ key: "Ctrl/Cmd + R",
+ description: t('kbd.shortcuts.table.ctrlRDescription'),
+ flag: "rightFill",
+ },
+ {
+ key: "Ctrl/Cmd + C",
+ description: t('kbd.shortcuts.table.ctrlCDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + V",
+ description: t('kbd.shortcuts.table.ctrlVDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + F",
+ description: t('kbd.shortcuts.table.ctrlFDescription'),
+ flag: "search",
+ disabled: true,
+ },
+ {
+ key: "Ctrl/Cmd + Home/End",
+ description: t('kbd.shortcuts.table.ctrlHomeEndDescription'),
+ flag: "first/last",
+ },
+ {
+ key: "Ctrl/Cmd + Shift + Home/End",
+ description: t('kbd.shortcuts.table.ctrlShiftHomeEndDescription'),
+ flag: "first/last",
+ },
+ ];
+};
+
+export const useDocumentKeyboardShortcuts = () => {
+ const { t } = useTranslation();
+
+ return [
+ {
+ key: "Ctrl/Cmd + B",
+ description: t('kbd.shortcuts.document.ctrlBDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + I",
+ description: t('kbd.shortcuts.document.ctrlIDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + U",
+ description: t('kbd.shortcuts.document.ctrlUDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + S",
+ description: t('kbd.shortcuts.document.ctrlSDescription'),
+ },
+ {
+ key: "#",
+ description: t('kbd.shortcuts.document.heading1Description'),
+ },
+ {
+ key: "##",
+ description: t('kbd.shortcuts.document.heading2Description'),
+ },
+ {
+ key: "###",
+ description: t('kbd.shortcuts.document.heading3Description'),
+ },
+ {
+ key: "[]",
+ description: t('kbd.shortcuts.document.checkboxDescription'),
+ },
+ {
+ key: "-",
+ description: t('kbd.shortcuts.document.unorderedListDescription'),
+ },
+ {
+ key: "number + .",
+ description: t('kbd.shortcuts.document.orderedListDescription'),
+ },
+ {
+ key: "```",
+ description: t('kbd.shortcuts.document.codeBlockDescription'),
+ },
+ {
+ key: "---",
+ description: t('kbd.shortcuts.document.horizontalRuleDescription'),
+ },
+ ];
+};
+
+export const useCommonKeyboardShortcuts = () => {
+ const { t } = useTranslation();
+
+ return [
+ {
+ key: "Ctrl/Cmd + /",
+ description: t('kbd.shortcuts.common.toggleChatbotDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + \\",
+ description: t('kbd.shortcuts.common.toggleSidebarDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + Shift + L",
+ description: t('kbd.shortcuts.common.toggleThemeDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + K",
+ description: t('kbd.shortcuts.common.toggleCommandPaletteDescription'),
+ },
+ {
+ key: "Ctrl/Cmd + ,",
+ description: t('kbd.shortcuts.common.openSettingsDescription'),
+ },
+ ];
+};
diff --git a/components/keyboard-shortcuts/index.tsx b/components/keyboard-shortcuts/index.tsx
index 4b51d622..be9d0a55 100644
--- a/components/keyboard-shortcuts/index.tsx
+++ b/components/keyboard-shortcuts/index.tsx
@@ -1,16 +1,23 @@
+import { useTranslation } from "react-i18next"
+
import { useAppRuntimeStore } from "@/lib/store/runtime-store"
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
import {
- CommonKeyboardShortcuts,
- DocumentKeyboardShortcuts,
- TableKeyboardShortcuts,
+ useCommonKeyboardShortcuts,
+ useDocumentKeyboardShortcuts,
+ useTableKeyboardShortcuts,
} from "./const"
import { ShortcutTable } from "./shortcut-table"
export function KeyboardShortCuts() {
const { isKeyboardShortcutsOpen, setKeyboardShortcutsOpen } =
useAppRuntimeStore()
+ const { t } = useTranslation()
+ const CommonKeyboardShortcuts = useCommonKeyboardShortcuts()
+ const DocumentKeyboardShortcuts = useDocumentKeyboardShortcuts()
+ const TableKeyboardShortcuts = useTableKeyboardShortcuts()
+
return (
diff --git a/components/keyboard-shortcuts/shortcut-table.tsx b/components/keyboard-shortcuts/shortcut-table.tsx
index 3e062f19..7cb817a4 100644
--- a/components/keyboard-shortcuts/shortcut-table.tsx
+++ b/components/keyboard-shortcuts/shortcut-table.tsx
@@ -1,3 +1,5 @@
+import { useTranslation } from "react-i18next"
+
import {
Table,
TableBody,
@@ -13,14 +15,15 @@ interface ShortcutTableProps {
title?: string
}
export const ShortcutTable = ({ shortcuts, title }: ShortcutTableProps) => {
+ const { t } = useTranslation()
return (
{title || "Keyboard Shortcuts"}
- Shortcut
- Description
+ {t("kbd.shortcuts.common.shortcut")}
+ {t("kbd.shortcuts.common.description")}
diff --git a/components/nav/dropdown-menu.tsx b/components/nav/dropdown-menu.tsx
index 64ce3fff..fd8792be 100644
--- a/components/nav/dropdown-menu.tsx
+++ b/components/nav/dropdown-menu.tsx
@@ -13,6 +13,7 @@ import {
ScanTextIcon,
Trash2Icon,
} from "lucide-react"
+import { useTranslation } from "react-i18next"
import { Link, useNavigate } from "react-router-dom"
import { BGEM3 } from "@/lib/ai/llm_vendors/bge"
@@ -48,7 +49,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { DiscordIcon } from "@/components/icons/discord"
-import { NodeUpdateTime } from "@/apps/web-app/[database]/[node]/node-update-time"
+import { NodeUpdateTime } from "@/components/nav/node-update-time"
import { useExperimentConfigStore } from "@/apps/web-app/settings/experiment/store"
import { CopyShowHide } from "../copy-show-hide"
@@ -60,6 +61,7 @@ import { VCardQrCode } from "../vcard-qr-code"
import { UpdateStatusComponent } from "./update-status"
export function NavDropdownMenu() {
+ const { t } = useTranslation()
const router = useNavigate()
const [open, setOpen] = useState(false)
const { hasEmbeddingModel, embeddingTexts } = useEmbedding()
@@ -98,7 +100,7 @@ export function NavDropdownMenu() {
const handleCreateDocEmbedding = async () => {
if (node) {
toast({
- title: `Creating Embedding for ${node.name}`,
+ title: t("nav.dropdown.menu.creatingEmbedding", { name: node.name }),
})
await createEmbedding({
id: node.id,
@@ -107,7 +109,7 @@ export function NavDropdownMenu() {
provider: new BGEM3(embeddingTexts),
})
toast({
- title: "Embedding Created",
+ title: t("nav.dropdown.menu.embeddingCreated"),
})
}
}
@@ -117,12 +119,12 @@ export function NavDropdownMenu() {
- Send mail to Eidos
+ {t("nav.dropdown.menu.sendMailToEidos")}
{node && (
@@ -131,10 +133,7 @@ export function NavDropdownMenu() {
{node &&
}
- 1. Scan the QR code to add the address to your contacts
-
- 2. Send an email to this address to save data into this table
-
+ {t("nav.dropdown.menu.emailInstructions")}
)}
@@ -148,21 +147,19 @@ export function NavDropdownMenu() {
- {/* All data hosted on Local 🖥 */}
- {/* */}
- Command Palette
+ {t("nav.dropdown.menu.commandPalette")}
⌘K
- Keyboard Shortcuts
+ {t("nav.dropdown.menu.keyboardShortcuts")}
- Settings
+ {t("common.settings")}
@@ -178,17 +175,10 @@ export function NavDropdownMenu() {
Discord
- {/*
-
-
- Wiki
-
- */}
-
- Website
+ {t("nav.dropdown.menu.website")}
@@ -204,7 +194,7 @@ export function NavDropdownMenu() {
toggleNodeFullWidth(node)
}}
>
- Full Width
+ {t("nav.dropdown.menu.fullWidth")}
- Lock
+ {t("nav.dropdown.menu.lock")}
>
@@ -225,20 +215,19 @@ export function NavDropdownMenu() {
- Mail
+ {t("nav.dropdown.menu.mail")}
>
)}
- {/* node related operate */}
{node.type === "doc" && (
<>
- Move Into
+ {t("node.menu.moveInto")}
@@ -251,7 +240,7 @@ export function NavDropdownMenu() {
- Embedding(Beta)
+ {t("nav.dropdown.menu.embedding")}
@@ -261,7 +250,7 @@ export function NavDropdownMenu() {
)}
- Delete
+ {t("common.delete")}
>
@@ -270,12 +259,17 @@ export function NavDropdownMenu() {
- Download
+ {t("common.download")}
- Version: {EIDOS_VERSION} ({isDesktopMode ? "Desktop" : "Web"})
+ {t("nav.dropdown.menu.version", {
+ version: EIDOS_VERSION,
+ mode: isDesktopMode
+ ? t("nav.dropdown.menu.desktop")
+ : t("nav.dropdown.menu.web"),
+ })}
diff --git a/components/nav/index.tsx b/components/nav/index.tsx
index f117286e..d00359c6 100644
--- a/components/nav/index.tsx
+++ b/components/nav/index.tsx
@@ -1,13 +1,13 @@
import { Menu, PanelRightIcon } from "lucide-react"
import { useTheme } from "next-themes"
+import { useSpaceAppStore } from "@/apps/web-app/[database]/store"
+import { Button } from "@/components/ui/button"
+import { useSidebar } from "@/components/ui/sidebar"
import { isDesktopMode } from "@/lib/env"
import { useAppStore } from "@/lib/store/app-store"
import { cn } from "@/lib/utils"
import { isMac } from "@/lib/web/helper"
-import { Button } from "@/components/ui/button"
-import { useSidebar } from "@/components/ui/sidebar"
-import { useSpaceAppStore } from "@/apps/web-app/[database]/store"
import { BreadCrumb } from "./breadcrumb"
import { NavDropdownMenu } from "./dropdown-menu"
@@ -43,7 +43,7 @@ export const Nav = ({ showMenu = true }: { showMenu?: boolean }) => {
"flex h-8 w-full border-separate items-center justify-between pl-2 shrink-0",
{
fixed: navigator.windowControlsOverlay?.visible,
- "!ml-[72px]":
+ "!pl-[72px]":
(isDesktopMode || navigator.windowControlsOverlay?.visible) &&
isMac() &&
!isSidebarOpen,
diff --git a/components/nav/nav-status.tsx b/components/nav/nav-status.tsx
index be2662db..31ed21d9 100644
--- a/components/nav/nav-status.tsx
+++ b/components/nav/nav-status.tsx
@@ -8,6 +8,7 @@ import {
PinOffIcon,
Unplug
} from "lucide-react"
+import { useTranslation } from "react-i18next"
import { AvatarList } from "@/components/avatar-list"
import { Button } from "@/components/ui/button"
@@ -53,6 +54,7 @@ const AppInfoMap: Record<
}
export const NavStatus = () => {
+ const { t } = useTranslation()
const {
isRightPanelOpen,
setIsRightPanelOpen,
@@ -84,16 +86,16 @@ export const NavStatus = () => {
- Locked
+ {t('common.lock')}
)}
{!isDesktopMode && (
{connected ? (
@@ -130,8 +132,8 @@ export const NavStatus = () => {
{currentNode?.is_pinned
- ? "Click to unpin this node"
- : "Click to pin this node"}
+ ? t('nav.status.clickToUnpin')
+ : t('nav.status.clickToPin')}
diff --git a/apps/web-app/[database]/[node]/node-update-time.tsx b/components/nav/node-update-time.tsx
similarity index 69%
rename from apps/web-app/[database]/[node]/node-update-time.tsx
rename to components/nav/node-update-time.tsx
index 58f301ce..6a3bea17 100644
--- a/apps/web-app/[database]/[node]/node-update-time.tsx
+++ b/components/nav/node-update-time.tsx
@@ -1,12 +1,17 @@
+import { useTranslation } from "react-i18next"
+
import { timeAgo } from "@/lib/utils"
import { useCurrentNode } from "@/hooks/use-current-node"
import { useNodeBaseInfo } from "@/hooks/use-node-base-info"
export const NodeUpdateTime = () => {
const node = useCurrentNode()
+ const { t } = useTranslation()
const { updated_at } = useNodeBaseInfo(node)
const tips = updated_at
- ? "last updated: " + timeAgo(new Date(updated_at + "Z"))
+ ? t("nav.dropdown.menu.lastUpdated", {
+ time: timeAgo(new Date(updated_at + "Z")),
+ })
: ""
if (!updated_at?.length) return null
return
{tips}
diff --git a/components/nav/update-status.tsx b/components/nav/update-status.tsx
index b38313ce..8712f146 100644
--- a/components/nav/update-status.tsx
+++ b/components/nav/update-status.tsx
@@ -1,11 +1,13 @@
import { useEffect } from "react"
import { Download, RefreshCw } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { isDesktopMode } from "@/lib/env"
import { useUpdateStatus } from "@/hooks/use-update-status"
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
export function UpdateStatusComponent() {
+ const { t } = useTranslation()
const { updateStatus, updateInfo, checkForUpdates, quitAndInstall } =
useUpdateStatus()
@@ -30,17 +32,17 @@ export function UpdateStatusComponent() {
{updateStatus === "available" && (
- Update to v{updateInfo?.version}
+ {t('nav.status.updateAvailable', { version: updateInfo?.version })}
)}
{updateStatus === "not-available" && (
- No updates available
+ {t('nav.status.noUpdatesAvailable')}
)}
- Check for updates
+ {t('nav.status.checkForUpdates')}
>
)
diff --git a/components/node-menu/move-into.tsx b/components/node-menu/move-into.tsx
index b6334675..8852097f 100644
--- a/components/node-menu/move-into.tsx
+++ b/components/node-menu/move-into.tsx
@@ -1,3 +1,5 @@
+import { useTranslation } from "react-i18next"
+
import { ITreeNode } from "@/lib/store/ITreeNode"
import { useNodeTree } from "@/hooks/use-node-tree"
import { useAllNodes } from "@/hooks/use-nodes"
@@ -19,6 +21,7 @@ export const NodeMoveInto = ({ node }: { node: ITreeNode }) => {
type: ["table", "folder"],
isDeleted: false,
})
+ const { t } = useTranslation()
const { sqlite } = useSqlite()
const { setNode } = useNodeTree()
@@ -32,10 +35,10 @@ export const NodeMoveInto = ({ node }: { node: ITreeNode }) => {
}
return (
-
+
- No table found.
+ {t("common.noTableFound")}
{tableNodes.map((tableNode, index) => (
{
const { sqlite } = useSqlite()
+ const { t } = useTranslation()
const exportDoc = async (docId: string) => {
const file = await sqlite?.exportMarkdown(docId)
@@ -42,7 +44,7 @@ export const NodeExportContextMenu = ({ node }: { node: ITreeNode }) => {
- Export{" "}
+ {t("common.export")}
{
- Export
+ {t("common.export")}
@@ -80,6 +82,7 @@ export const NodeExportContextMenu = ({ node }: { node: ITreeNode }) => {
export const NodeExport = ({ node }: { node: ITreeNode }) => {
const { sqlite } = useSqlite()
+ const { t } = useTranslation()
const exportDoc = async (docId: string) => {
const md = await sqlite?.exportMarkdown(docId)
@@ -104,7 +107,7 @@ export const NodeExport = ({ node }: { node: ITreeNode }) => {
- Export
+ {t("common.export")}
{
- Export
+ {t("common.export")}
{
const { sqlite } = useSqlite(space)
+ const { t } = useTranslation()
const { convertMarkdown2State } = useDocEditor(sqlite)
const [progress, setProgress] = useState(0)
const [importing, setImporting] = useState(false)
@@ -82,26 +84,26 @@ export const EverydaySidebarItem = ({ space }: { space: string }) => {
>
- Today
+ {t("common.today")}
- Open
- Download
+ {t("common.open")}
+ {t("common.download")}
- Import
+ {t("common.import")}
- Import From Logseq(Beta)
+ {t("everyday.import.title")}
-
click here to select logseq journal folder
+
{t("everyday.import.selectFolder")}
{importing && }
diff --git a/components/sidebar/import-file/index.tsx b/components/sidebar/import-file/index.tsx
index 5935dd0d..10892371 100644
--- a/components/sidebar/import-file/index.tsx
+++ b/components/sidebar/import-file/index.tsx
@@ -1,5 +1,6 @@
import { useState } from "react"
import { Plus } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { Button } from "@/components/ui/button"
import {
@@ -16,7 +17,7 @@ import { ImportTable } from "./import-table"
export function ImportFileDialog() {
const [open, setOpen] = useState(false)
-
+ const { t } = useTranslation()
return (
@@ -28,20 +29,22 @@ export function ImportFileDialog() {
>
- Import
+ {t("common.import")}
- Import File
+ {t("sidebar.importFile.title")}
- Import a CSV file to create a new table Import a markdown
- file to create a new document
+ {t("sidebar.importFile.importCSVDescription")}
+ {t("sidebar.importFile.importMarkdownDescription")}
+
+
diff --git a/components/sidebar/index.tsx b/components/sidebar/index.tsx
index 061dd1b9..37eac257 100644
--- a/components/sidebar/index.tsx
+++ b/components/sidebar/index.tsx
@@ -10,6 +10,7 @@ import {
PinIcon,
} from "lucide-react"
import { Link } from "react-router-dom"
+import { useTranslation } from "react-i18next"
import { isDesktopMode } from "@/lib/env"
import { useAppStore } from "@/lib/store/app-store"
@@ -44,6 +45,7 @@ import { useTreeOperations } from "./tree/hooks"
import { useFolderStore } from "./tree/store"
export const SideBar = ({ className }: any) => {
+ const { t } = useTranslation()
const { space } = useCurrentPathInfo()
const [loading, setLoading] = useState(true)
const { updateNodeList } = useSqlite(space)
@@ -86,7 +88,7 @@ export const SideBar = ({ className }: any) => {
{isShareMode ? (
- "ShareMode"
+ t("common.shareMode")
) : (
<>
@@ -109,7 +111,7 @@ export const SideBar = ({ className }: any) => {
className="w-full justify-start font-normal"
>
- Files
+ {t("common.files")}
)}
{
>
- Extensions
+ {t("common.extensions")}
node.is_pinned)}
Icon={ }
disableAdd
@@ -134,7 +136,7 @@ export const SideBar = ({ className }: any) => {
!node.parent_id && !node.is_deleted
)}
@@ -147,7 +149,7 @@ export const SideBar = ({ className }: any) => {
disabled={!currentCut}
>
- Paste
+ {t("common.paste")}
@@ -172,8 +174,6 @@ export const SideBar = ({ className }: any) => {
- {/* */}
- {/* */}
)}
diff --git a/components/sidebar/trash/index.tsx b/components/sidebar/trash/index.tsx
index 8dbab4ff..c3720b60 100644
--- a/components/sidebar/trash/index.tsx
+++ b/components/sidebar/trash/index.tsx
@@ -1,5 +1,6 @@
import { useMemo, useState } from "react"
import { Trash2Icon, Undo2Icon } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { ITreeNode } from "@/lib/store/ITreeNode"
@@ -31,6 +32,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"
export const Trash = () => {
const [open, setOpen] = useState(false)
+ const { t } = useTranslation()
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const allDeletedNodes = useAllNodes({ isDeleted: true })
const { restoreNode, permanentlyDeleteNode } = useSqlite()
@@ -88,26 +90,29 @@ export const Trash = () => {
asChild
>
- Trash
+
+ {t("common.trash")}
- Trash
+ {t("common.trash")}
- restore or permanently delete nodes
+ {t("sidebar.trash.restoreOrPermanentlyDeleteNodes")}
setSearch(e.target.value)}
>
- {!Boolean(allDeletedNodes.length) && Trash is empty
}
- {!Boolean(allNodes.length) && no results found
}
+ {!Boolean(allDeletedNodes.length) && (
+ {t("sidebar.trash.trashIsEmpty")}
+ )}
+ {!Boolean(allNodes.length) && {t("common.noResultsFound")}
}
{allNodes.map((node) => {
return (
{
- Are you absolutely sure?
+
+ {t("common.areYouAbsolutelySure")}
+
- This action cannot be undone. This will permanently delete the
- node
+ {t("sidebar.trash.thisActionCannotBeUndone")}
- Cancel
+ {t("common.cancel")}
- Continue
+ {t("common.continue")}
diff --git a/components/sidebar/tree/create-node-trigger.tsx b/components/sidebar/tree/create-node-trigger.tsx
index 10ff2852..c06004c8 100644
--- a/components/sidebar/tree/create-node-trigger.tsx
+++ b/components/sidebar/tree/create-node-trigger.tsx
@@ -1,4 +1,5 @@
import { Plus } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { ITreeNode } from "@/lib/store/ITreeNode"
import { useCurrentPathInfo } from "@/hooks/use-current-pathinfo"
@@ -14,6 +15,7 @@ import {
export const CreateNodeTrigger = ({ parent_id }: { parent_id?: string }) => {
const { space } = useCurrentPathInfo()
+ const { t } = useTranslation()
const { createDoc, createTable, createFolder } = useSqlite(space)
const goto = useGoto()
@@ -59,21 +61,21 @@ export const CreateNodeTrigger = ({ parent_id }: { parent_id?: string }) => {
handleCreateNode("doc")
}}
>
- New Doc
+ {t("node.menu.newDoc")}
{
handleCreateNode("table")
}}
>
- New Table
+ {t("node.menu.newTable")}
{
handleCreateNode("folder")
}}
>
- New Folder
+ {t("node.menu.newFolder")}
diff --git a/components/sidebar/tree/node-menu.tsx b/components/sidebar/tree/node-menu.tsx
index 3f44d9d2..f8f64815 100644
--- a/components/sidebar/tree/node-menu.tsx
+++ b/components/sidebar/tree/node-menu.tsx
@@ -13,6 +13,7 @@ import {
ScissorsIcon,
Trash2Icon,
} from "lucide-react"
+import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { isInkServiceMode } from "@/lib/env"
@@ -56,6 +57,7 @@ export function NodeItem({
node,
depth,
}: INodeItemProps) {
+ const { t } = useTranslation()
const {
createDoc,
createTable,
@@ -89,10 +91,10 @@ export function NodeItem({
useClickAway(() => {
if (renameOpen) {
- renameNode(node.id, newName);
- setRenameOpen(false);
+ renameNode(node.id, newName)
+ setRenameOpen(false)
}
- }, [renameInputRef]);
+ }, [renameInputRef])
const router = useNavigate()
@@ -110,12 +112,12 @@ export function NodeItem({
const handleRenameKeyDown = (e: React.KeyboardEvent
) => {
if (e.key === "Enter") {
- renameNode(node.id, newName);
- setRenameOpen(false);
+ renameNode(node.id, newName)
+ setRenameOpen(false)
}
if (e.key === "Escape") {
- setRenameOpen(false);
- setNewName(node.name); // Reset to original name when canceling
+ setRenameOpen(false)
+ setNewName(node.name) // Reset to original name when canceling
}
}
if (isInkServiceMode) {
@@ -145,11 +147,11 @@ export function NodeItem({
- Delete
+ {t("common.delete")}
- Rename
+ {t("node.menu.rename")}
- {currentCut === node.id ? "Cancel cut" : "Cut"}
+ {currentCut === node.id
+ ? t("node.menu.cancelCut")
+ : t("node.menu.cut")}
{node.type === "folder" && (
@@ -166,7 +170,7 @@ export function NodeItem({
disabled={!currentCut}
>
- Paste
+ {t("common.paste")}
)}
@@ -175,12 +179,12 @@ export function NodeItem({
{node.is_pinned ? (
unpin(node.id)}>
- Unpin
+ {t("node.menu.unpin")}
) : (
pin(node.id)}>
- Pin
+ {t("node.menu.pin")}
)}
>
@@ -191,15 +195,15 @@ export function NodeItem({
<>
- New Doc
+ {t("node.menu.newDoc")}
- New Table
+ {t("node.menu.newTable")}
6}>
- New Nested Folder
+ {t("node.menu.newNestedFolder")}
>
)}
@@ -210,8 +214,7 @@ export function NodeItem({
disabled
>
- Duplicate
- {/* ⌘R */}
+ {t("node.menu.duplicate")}
>
)}
@@ -220,7 +223,7 @@ export function NodeItem({
- Move Into
+ {t("node.menu.moveInto")}
diff --git a/components/space-settings/index.tsx b/components/space-settings/index.tsx
index d189bf01..bace9825 100644
--- a/components/space-settings/index.tsx
+++ b/components/space-settings/index.tsx
@@ -1,6 +1,7 @@
import { useState } from "react"
import { SettingsIcon } from "lucide-react"
import { Link, useNavigate } from "react-router-dom"
+import { useTranslation } from "react-i18next"
import { useCurrentPathInfo } from "@/hooks/use-current-pathinfo"
import { useSpace } from "@/hooks/use-space"
@@ -29,6 +30,7 @@ import { Label } from "@/components/ui/label"
import { Button } from "../ui/button"
export function Settings() {
+ const { t } = useTranslation()
const { space } = useCurrentPathInfo()
const { exportSpace, deleteSpace, rebuildIndex } = useSpace()
const navigate = useNavigate()
@@ -65,26 +67,25 @@ export function Settings() {
return (
- Space Settings
+ {t('space.settings.title')}
- Settings only apply to this space. if you want to change settings for
- all spaces, go to{" "}
+ {t('space.settings.description')}{" "}
- global settings
+ {t('space.settings.globalSettings')}
- Name
+ {t('common.name')}
-
Export
+
{t('common.export')}
- Export all data from this space for backup or transfer purposes.
+ {t('space.settings.exportDescription')}
- Export Space
+ {t('space.settings.exportSpace')}
-
Rebuild Index
+
{t('space.settings.rebuildIndex')}
- Reconstruct the search index for this space. Use this if you're
- experiencing search issues.
+ {t('space.settings.rebuildIndexDescription')}
- {isRebuilding ? "Rebuilding..." : "Rebuild Index"}
+ {isRebuilding ? t('space.settings.rebuilding') : t('space.settings.rebuildIndex')}
-
Danger zone
+
{t('space.settings.dangerZone')}
- Permanently delete this space and all its contents. This action
- cannot be undone.
+ {t('space.settings.deleteSpaceDescription')}
- Delete Space
+ {t('space.settings.deleteSpace')}
- Are you absolutely sure?
+ {t('common.areYouAbsolutelySure')}
- This action cannot be undone. This will permanently delete
- your space{" "}
- {space} .
- Please type the space name to confirm.
+ {t('space.settings.deleteSpaceWarning', { spaceName: space })}
setConfirmName(e.target.value)}
/>
- Cancel
+ {t('common.cancel')}
- Continue
+ {t('common.continue')}
@@ -159,6 +155,7 @@ export function Settings() {
}
export const SpaceSettings = () => {
+ const { t } = useTranslation()
return (
@@ -169,7 +166,7 @@ export const SpaceSettings = () => {
asChild
>
- Settings
+ {t('common.settings')}
diff --git a/components/table/field-selector.tsx b/components/table/field-selector.tsx
index fd9489f5..d2823f5e 100644
--- a/components/table/field-selector.tsx
+++ b/components/table/field-selector.tsx
@@ -1,5 +1,6 @@
import { useState } from "react"
import { ChevronsUpDown } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { IField } from "@/lib/store/interface"
import {
@@ -32,10 +33,13 @@ export const FieldSelector = ({
onChange,
}: IFieldSelectorProps) => {
const [open, setOpen] = useState(false)
+ const { t } = useTranslation()
+
const handleSelect = (field: IField) => {
onChange(field.table_column_name)
setOpen(false)
}
+
return (
@@ -47,16 +51,16 @@ export const FieldSelector = ({
{value
? fields.find((field) => field.table_column_name === value)
- ?.name || "Untitled Field"
- : "Select Field"}
+ ?.name || t("table.field.untitledField")
+ : t("table.field.selectField")}
-
- No field found.
+
+ {t("table.field.noFieldFound")}
{fields.map((field) => {
const iconSvgString = icons[field.type]({
@@ -76,7 +80,7 @@ export const FieldSelector = ({
}}
>
- {field.name || "Untitled Field"}
+ {field.name || t("table.field.untitledField")}
diff --git a/components/table/view-editor/view-editor.tsx b/components/table/view-editor/view-editor.tsx
index 39764586..480b3ead 100644
--- a/components/table/view-editor/view-editor.tsx
+++ b/components/table/view-editor/view-editor.tsx
@@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useClickAway } from "ahooks"
import { useForm } from "react-hook-form"
import * as z from "zod"
+import { useTranslation } from "react-i18next"
import { IView, ViewTypeEnum } from "@/lib/store/IView"
import { Input } from "@/components/ui/input"
@@ -36,6 +37,7 @@ interface IViewEditorProps {
const LIMIT_ROWS_FOR_OPTIMIZE_VIEW = 88888
export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
+ const { t } = useTranslation()
const ref = useRef(null)
const { updateView } = useViewOperation()
const { count, loading } = useViewCount(view)
@@ -83,8 +85,7 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
name="name"
render={({ field }) => (
- Name
-
+ {t('common.name')}
@@ -98,9 +99,9 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
name="type"
render={({ field }) => (
- Type
+ {t('table.fieldType')}
- The type of view to use for this table.
+ {t('table.view.typeDescription')}
@@ -111,7 +112,7 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
}}
viewId={view.id}
isActive={field.value === "grid"}
- title="Grid"
+ title={t('table.view.grid')}
viewType={ViewTypeEnum.Grid}
icon={ViewIconMap[ViewTypeEnum.Grid]}
>
@@ -123,7 +124,7 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
disabled={disabled}
viewId={view.id}
isActive={field.value === "gallery"}
- title="Gallery"
+ title={t('table.view.gallery')}
viewType={ViewTypeEnum.Gallery}
icon={ViewIconMap[ViewTypeEnum.Gallery]}
>
@@ -135,13 +136,13 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
}}
disabled={disabled}
isActive={field.value === "doc_list"}
- title="Doc list (beta)"
+ title={t('table.view.docList')}
viewType={ViewTypeEnum.DocList}
icon={ViewIconMap[ViewTypeEnum.DocList]}
>
{disabled && (
- The disabled view types are not ready for large data
+ {t('table.view.disabledViewTypesWarning')}
)}
@@ -156,9 +157,9 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
name="query"
render={({ field }) => (
- Query
+ {t('table.view.query')}
- sql query to use for this view.
+ {t('table.view.queryDescription')}
@@ -167,7 +168,7 @@ export const ViewEditor = ({ setEditDialogOpen, view }: IViewEditorProps) => {
)}
/>
- update
+ {t('common.update')}
diff --git a/components/table/view-field/view-field.tsx b/components/table/view-field/view-field.tsx
index d7bb04a9..58c7af01 100644
--- a/components/table/view-field/view-field.tsx
+++ b/components/table/view-field/view-field.tsx
@@ -5,6 +5,7 @@ import { sortBy } from "lodash"
import { ArrowDownUpIcon, SlidersHorizontalIcon } from "lucide-react"
import { DndProvider } from "react-dnd"
import { HTML5Backend } from "react-dnd-html5-backend"
+import { useTranslation } from "react-i18next"
import { IView } from "@/lib/store/IView"
import { IField } from "@/lib/store/interface"
@@ -26,6 +27,7 @@ export interface ContainerState {
}
export const ViewField = (props: { view?: IView }) => {
+ const { t } = useTranslation()
const [open, setOpen] = useState(false)
const orderMap = useMemo(
() => props.view?.order_map || {},
@@ -146,10 +148,10 @@ export const ViewField = (props: { view?: IView }) => {
- show all
+ {t('table.view.field.showAll')}
- hide all
+ {t('table.view.field.hideAll')}
@@ -161,7 +163,7 @@ export const ViewField = (props: { view?: IView }) => {
- Add Field
+ {t('table.view.field.addField')}
diff --git a/components/table/view-filter-editor/view-filter-editor.tsx b/components/table/view-filter-editor/view-filter-editor.tsx
index da1dd1c2..b0245c56 100644
--- a/components/table/view-filter-editor/view-filter-editor.tsx
+++ b/components/table/view-filter-editor/view-filter-editor.tsx
@@ -1,4 +1,5 @@
import { CopyPlusIcon, PlusIcon } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { BinaryOperator, CompareOperator } from "@/lib/fields/const"
import { IField } from "@/lib/store/interface"
@@ -32,6 +33,8 @@ export const ViewFilterEditor = ({
handleClearFilter,
depth = 0,
}: IViewFilterEditorProps) => {
+ const { t } = useTranslation()
+
const handleAddFilter = () => {
const newValue = _value
? {
@@ -94,12 +97,12 @@ export const ViewFilterEditor = ({
className="flex cursor-pointer items-center gap-2 rounded-sm p-2 hover:bg-secondary"
>
- add filter
+ {t('table.view.addFilter')}
) : (
- add filter
+ {t('table.view.addFilter')}
- add filter
+ {t('table.view.addFilter')}
{depth < 2 && (
- add group filter
+ {t('table.view.addGroupFilter')}
)}
@@ -131,12 +134,12 @@ export const ViewFilterEditor = ({
})}
>
- There is no filter rule, add one
+ {t('table.view.noFilterRule')}
{AddFilterComponent}
- delete filter
+ {t('table.view.deleteFilter')}
)
@@ -164,7 +167,7 @@ export const ViewFilterEditor = ({
{AddFilterComponent}
- delete filter
+ {t('table.view.deleteFilter')}
)
diff --git a/components/table/view-filter-editor/view-filter-group-editor.tsx b/components/table/view-filter-editor/view-filter-group-editor.tsx
index 59d3c719..efdf0e17 100644
--- a/components/table/view-filter-editor/view-filter-group-editor.tsx
+++ b/components/table/view-filter-editor/view-filter-group-editor.tsx
@@ -1,5 +1,6 @@
import React from "react"
import { Trash2Icon } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { BinaryOperator } from "@/lib/fields/const"
import { isLogicOperator } from "@/lib/sqlite/sql-filter-parser"
@@ -30,6 +31,7 @@ export const ViewFilterGroupEditor = ({
depth = 0,
parentOperator,
}: IViewFilterGroupEditorProps) => {
+ const { t } = useTranslation()
const handleValueChange = (value: IGroupFilterValue, index: number) => {
const newValue = { ..._value, operands: [..._value.operands] }
newValue.operands[index] = value
@@ -54,7 +56,7 @@ export const ViewFilterGroupEditor = ({
{_value?.operands.map((operand, index) => {
return (
- {index === 0 && Where
}
+ {index === 0 && {t("table.view.where")}
}
{index === 1 && (
void
}) => {
+ const { t } = useTranslation()
return (
@@ -97,10 +100,14 @@ export const OpSelector = ({
- AND
+
+ {t("table.view.and")}
+
- OR
+
+ {t("table.view.or")}
+
diff --git a/components/table/view-item.tsx b/components/table/view-item.tsx
index 9b5a9593..99167d98 100644
--- a/components/table/view-item.tsx
+++ b/components/table/view-item.tsx
@@ -1,6 +1,7 @@
import { useContext, useState } from "react"
import { LayoutGridIcon, LayoutListIcon, Table2Icon } from "lucide-react"
import ReactDOM from "react-dom"
+import { useTranslation } from "react-i18next"
import { IView, ViewTypeEnum } from "@/lib/store/IView"
import { cn } from "@/lib/utils"
@@ -47,6 +48,7 @@ export const ViewItem = ({
deleteView,
disabledDelete,
}: IViewItemProps) => {
+ const { t } = useTranslation()
const [open, setOpen] = useState(false)
const { getLoading } = useViewLoadingStore()
const loading = getLoading(view.query)
@@ -92,31 +94,30 @@ export const ViewItem = ({
- Edit
+ {t('table.view.edit')}
- Delete
+ {t('common.delete')}
- Are you sure delete this view?
+ {t('table.view.deleteConfirmTitle')}
- This action cannot be undone. This will permanently delete the
- view
+ {t('table.view.deleteConfirmDescription')}
setDeleteDialogOpen(false)}
>
- Cancel
+ {t('common.cancel')}
- Delete
+ {t('common.delete')}
diff --git a/components/table/view-sort-editor.tsx b/components/table/view-sort-editor.tsx
index e7d11ca5..16b5239b 100644
--- a/components/table/view-sort-editor.tsx
+++ b/components/table/view-sort-editor.tsx
@@ -1,5 +1,6 @@
import { useCallback, useContext, useEffect, useMemo, useState } from "react"
import { XIcon } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { useUiColumns } from "@/hooks/use-ui-columns"
import { Button } from "@/components/ui/button"
@@ -24,6 +25,7 @@ interface IViewEditorProps {
onSortChange?: (sort: OrderByItem[]) => void
}
export function ViewSortEditor(props: IViewEditorProps) {
+ const { t } = useTranslation()
const { onSortChange } = props
const { tableName, space, viewId } = useContext(TableContext)
const { currentView } = useCurrentView({ tableName, space, viewId })
@@ -108,13 +110,12 @@ export function ViewSortEditor(props: IViewEditorProps) {
{!orderItems.length && (
- There is no sort rule, add one
+ {t("table.view.noSortRule")}
)}
{orderItems.map((item, index) => {
return (
- {/*
*/}
onOrderChange(value, index)}
>
-
+
- Ascending
- Descending
+ {t("table.sortAscending")}
+
+ {t("table.sortDescending")}
+
- {/* */}
- Add sort
+ {t("table.view.addSort")}
- Delete sort
+ {t("table.view.deleteSort")}
diff --git a/components/table/view-toolbar.tsx b/components/table/view-toolbar.tsx
index c26984ff..9f582264 100644
--- a/components/table/view-toolbar.tsx
+++ b/components/table/view-toolbar.tsx
@@ -39,6 +39,7 @@ import { ViewField } from "./view-field/view-field"
import { ViewFilter } from "./view-filter"
import { ViewItem } from "./view-item"
import { ViewSort } from "./view-sort"
+import { useTranslation } from "react-i18next"
const useGap = (
width: number | undefined,
@@ -158,6 +159,7 @@ export const ViewToolbar = (props: {
const [open, setOpen] = useState(false)
const tableId = getTableIdByRawTableName(tableName)
const { subPageId, setSubPage, clearSubPage } = useCurrentSubPage()
+ const { t } = useTranslation()
const handleAddRow = async () => {
const uuid = uuidv7()
@@ -261,7 +263,7 @@ export const ViewToolbar = (props: {
{!props.isReadOnly && (
- New
+ {t('common.new')}
)}
diff --git a/components/table/views/gallery/properties.tsx b/components/table/views/gallery/properties.tsx
index 41506c59..c6c6d14b 100644
--- a/components/table/views/gallery/properties.tsx
+++ b/components/table/views/gallery/properties.tsx
@@ -2,6 +2,7 @@ import { useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
+import { useTranslation } from "react-i18next"
import { Button } from "@/components/ui/button"
import {
@@ -42,22 +43,15 @@ export const GalleryViewProperties = (props: { viewId: string }) => {
},
})
const [popoverOpen, setPopoverOpen] = useState(false)
+ const { t } = useTranslation()
const onSubmit = (data: IGalleryViewProperties) => console.log(data)
const fileFields = useFileFields()
const coverPreviewItems = [
- // {
- // value: null,
- // label: "None",
- // },
- // {
- // value: "cover",
- // label: "Cover",
- // },
{
value: "content",
- label: "Content",
+ label: t("table.view.gallery.content"),
},
...fileFields.map((field) => ({
value: field.table_column_name,
@@ -78,7 +72,7 @@ export const GalleryViewProperties = (props: { viewId: string }) => {
name="hideEmptyFields"
render={({ field }) => (
- Hide empty fields
+ {t("table.view.gallery.hideEmptyFields")}
{
@@ -101,7 +95,7 @@ export const GalleryViewProperties = (props: { viewId: string }) => {
name="coverPreview"
render={({ field }) => (
- Cover preview
+ {t("table.view.gallery.coverPreview")}
diff --git a/components/table/views/grid/fields/field-append-panel.tsx b/components/table/views/grid/fields/field-append-panel.tsx
index 7d77bac6..07990a8f 100644
--- a/components/table/views/grid/fields/field-append-panel.tsx
+++ b/components/table/views/grid/fields/field-append-panel.tsx
@@ -1,5 +1,6 @@
import * as React from "react"
import { useClickAway } from "ahooks"
+import { useTranslation } from "react-i18next"
import {
BaselineIcon,
CalendarDaysIcon,
@@ -40,58 +41,58 @@ export function FieldAppendPanel({
) => Promise
uiColumns: IField[]
}) {
+ const { t } = useTranslation()
const [currentField, setCurrentField] = React.useState()
const { tableName } = useCurrentPathInfo()
const ref = React.useRef(null)
const { isAddFieldEditorOpen, setIsAddFieldEditorOpen } = useTableAppStore()
const fieldTypes = [
- { name: "Text", value: FieldType.Text, icon: BaselineIcon },
- { name: "Number", value: FieldType.Number, icon: HashIcon },
- { name: "Select", value: FieldType.Select, icon: TagIcon },
- { name: "MultiSelect", value: FieldType.MultiSelect, icon: TagsIcon },
+ { name: t("table.field.text"), value: FieldType.Text, icon: BaselineIcon },
+ { name: t("table.field.number"), value: FieldType.Number, icon: HashIcon },
+ { name: t("table.field.select"), value: FieldType.Select, icon: TagIcon },
+ { name: t("table.field.multiSelect"), value: FieldType.MultiSelect, icon: TagsIcon },
{
- name: "Checkbox",
+ name: t("table.field.checkbox"),
value: FieldType.Checkbox,
icon: CheckSquareIcon,
},
- { name: "Rating", value: FieldType.Rating, icon: StarIcon },
-
- { name: "URL", value: FieldType.URL, icon: Link2Icon },
- { name: "Date", value: FieldType.Date, icon: CalendarDaysIcon },
- { name: "Files", value: FieldType.File, icon: ImageIcon },
+ { name: t("table.field.rating"), value: FieldType.Rating, icon: StarIcon },
+ { name: t("table.field.url"), value: FieldType.URL, icon: Link2Icon },
+ { name: t("table.field.date"), value: FieldType.Date, icon: CalendarDaysIcon },
+ { name: t("table.field.file"), value: FieldType.File, icon: ImageIcon },
{
- name: "Formula",
+ name: t("table.field.formula"),
value: FieldType.Formula,
icon: SigmaIcon,
},
{
- name: "Link",
+ name: t("table.field.link"),
value: FieldType.Link,
icon: LinkIcon,
disable: false,
},
{
- name: "Lookup",
+ name: t("table.field.lookup"),
value: FieldType.Lookup,
icon: TextSearchIcon,
},
{
- name: "Created Time",
+ name: t("table.field.createdTime"),
value: FieldType.CreatedTime,
icon: Clock3Icon,
},
{
- name: "Last Edited Time",
+ name: t("table.field.lastEditedTime"),
value: FieldType.LastEditedTime,
icon: Clock3Icon,
},
{
- name: "Created By",
+ name: t("table.field.createdBy"),
value: FieldType.CreatedBy,
icon: UserIcon,
},
{
- name: "Last Edited By",
+ name: t("table.field.lastEditedBy"),
value: FieldType.LastEditedBy,
icon: UserIcon,
},
@@ -187,7 +188,7 @@ export function FieldAppendPanel({
) : (
- add field
+ {t("table.field.addField")}
{fieldTypes.map((field, i) => {
diff --git a/components/table/views/grid/fields/field-delete.tsx b/components/table/views/grid/fields/field-delete.tsx
index adc25b95..9f37521f 100644
--- a/components/table/views/grid/fields/field-delete.tsx
+++ b/components/table/views/grid/fields/field-delete.tsx
@@ -1,4 +1,5 @@
import { useState } from "react"
+import { useTranslation } from "react-i18next"
import { FieldType } from "@/lib/fields/const"
import { IField } from "@/lib/store/interface"
@@ -24,6 +25,7 @@ export const FieldDelete = ({
children,
deleteField,
}: IFieldDeleteProps) => {
+ const { t } = useTranslation()
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const handleDeleteFieldConfirm = () => {
deleteField(field.table_column_name)
@@ -34,19 +36,19 @@ export const FieldDelete = ({
{children}
- Are you sure delete this field?
+ {t('table.field.deleteConfirmTitle')}
{field.type === FieldType.Link
- ? "This field is a link field. Deleting this field will also delete the paired field. and this action cannot be undone."
- : "This action cannot be undone."}
+ ? t('table.field.deleteLinkFieldWarning')
+ : t('common.thisActionCannotBeUndone')}
setIsDeleteDialogOpen(false)}>
- Cancel
+ {t('common.cancel')}
- Delete
+ {t('common.delete')}
diff --git a/components/table/views/grid/fields/field-editor-dropdown.tsx b/components/table/views/grid/fields/field-editor-dropdown.tsx
index d2413d11..4e17fcd2 100644
--- a/components/table/views/grid/fields/field-editor-dropdown.tsx
+++ b/components/table/views/grid/fields/field-editor-dropdown.tsx
@@ -7,6 +7,7 @@ import {
Trash2,
} from "lucide-react"
import { useLayer } from "react-laag"
+import { useTranslation } from 'react-i18next';
import { FieldType } from "@/lib/fields/const"
import { IView } from "@/lib/store/IView"
@@ -60,6 +61,7 @@ export const FieldEditorDropdown = (props: IFieldEditorDropdownProps) => {
const inputRef = useRef
(null)
const { fields } = useTableFields(tableName)
const { showColumns } = useColumns(fields, props.view)
+ const { t } = useTranslation();
useEffect(() => {
const currentField = showColumns[currentColIndex!]
@@ -167,15 +169,15 @@ export const FieldEditorDropdown = (props: IFieldEditorDropdownProps) => {
onClick={handleEditFieldPropertiesClick}
>
- Edit Property
+ {t('table.editProperty')}
- Sort Ascending
+ {t('table.sortAscending')}
- Sort Descending
+ {t('table.sortDescending')}
{currentUiColumn?.type !== "title" && (
{
>
- Delete Field
+ {t('table.deleteField')}
)}
- Are you sure delete this field?
+ {t('table.deleteFieldConfirmation')}
{currentUiColumn?.type === FieldType.Link
- ? "This field is a link field. Deleting this field will also delete the paired field. and this action cannot be undone."
- : "This action cannot be undone."}
+ ? t('table.deleteLinkFieldWarning')
+ : t('common.thisActionCannotBeUndone')}
@@ -202,13 +204,13 @@ export const FieldEditorDropdown = (props: IFieldEditorDropdownProps) => {
variant="ghost"
onClick={() => setIsDeleteDialogOpen(false)}
>
- Cancel
+ {t('common.cancel')}
- Delete
+ {t('common.delete')}
diff --git a/components/table/views/grid/fields/field-property-editor.tsx b/components/table/views/grid/fields/field-property-editor.tsx
index 91d964a8..2bc9cba5 100644
--- a/components/table/views/grid/fields/field-property-editor.tsx
+++ b/components/table/views/grid/fields/field-property-editor.tsx
@@ -1,6 +1,7 @@
import React from "react"
import { useClickAway } from "ahooks"
import { Trash2 } from "lucide-react"
+import { useTranslation } from 'react-i18next';
import { FieldType } from "@/lib/fields/const"
import { IField } from "@/lib/store/interface"
@@ -54,6 +55,7 @@ export const FieldPropertyEditor = ({
databaseName,
deleteField,
}: IFieldPropertyEditorProps) => {
+ const { t } = useTranslation();
const ref = React.useRef(null)
const { setIsFieldPropertiesEditorOpen, currentUiColumn: currentField } =
useTableAppStore()
@@ -95,7 +97,7 @@ export const FieldPropertyEditor = ({
-
Name
+
{t('common.name')}
-
Type
+
{t('table.fieldType')}
- Delete Field
+ {t('table.deleteField')}
)}
diff --git a/components/table/views/grid/fields/field-type-select.tsx b/components/table/views/grid/fields/field-type-select.tsx
index 87e70ddd..49badde6 100644
--- a/components/table/views/grid/fields/field-type-select.tsx
+++ b/components/table/views/grid/fields/field-type-select.tsx
@@ -1,5 +1,6 @@
import * as React from "react"
import { Check, ChevronsUpDown } from "lucide-react"
+import { useTranslation } from "react-i18next"
import { FieldType } from "@/lib/fields/const"
import { cn } from "@/lib/utils"
@@ -20,77 +21,26 @@ import { FieldIcon } from "@/components/table/field-icon"
// for now only support these fields
const fields = [
- {
- value: FieldType.Text,
- label: "Text",
- },
- {
- value: FieldType.Number,
- label: "Number",
- },
- {
- value: FieldType.Select,
- label: "Select",
- },
- {
- value: FieldType.MultiSelect,
- label: "Multi-select",
- },
- {
- value: FieldType.Checkbox,
- label: "Checkbox",
- },
- {
- value: FieldType.Rating,
- label: "Rating",
- },
- {
- value: FieldType.URL,
- label: "URL",
- },
- {
- value: FieldType.Date,
- label: "Date",
- },
- {
- value: FieldType.File,
- label: "File",
- },
+ { value: FieldType.Text, label: "table.field.text" },
+ { value: FieldType.Number, label: "table.field.number" },
+ { value: FieldType.Select, label: "table.field.select" },
+ { value: FieldType.MultiSelect, label: "table.field.multiSelect" },
+ { value: FieldType.Checkbox, label: "table.field.checkbox" },
+ { value: FieldType.Rating, label: "table.field.rating" },
+ { value: FieldType.URL, label: "table.field.url" },
+ { value: FieldType.Date, label: "table.field.date" },
+ { value: FieldType.File, label: "table.field.file" },
]
const readonlyFields = [
- {
- value: FieldType.Title,
- label: "Title",
- },
- {
- value: FieldType.Formula,
- label: "Formula",
- },
- {
- value: FieldType.Link,
- label: "Link",
- },
- {
- value: FieldType.Lookup,
- label: "Lookup",
- },
- {
- value: FieldType.CreatedTime,
- label: "Created Time",
- },
- {
- value: FieldType.LastEditedTime,
- label: "Last Edited Time",
- },
- {
- value: FieldType.CreatedBy,
- label: "Created By",
- },
- {
- value: FieldType.LastEditedBy,
- label: "Last Edited By",
- },
+ { value: FieldType.Title, label: "table.field.title" },
+ { value: FieldType.Formula, label: "table.field.formula" },
+ { value: FieldType.Link, label: "table.field.link" },
+ { value: FieldType.Lookup, label: "table.field.lookup" },
+ { value: FieldType.CreatedTime, label: "table.field.createdTime" },
+ { value: FieldType.LastEditedTime, label: "table.field.lastEditedTime" },
+ { value: FieldType.CreatedBy, label: "table.field.createdBy" },
+ { value: FieldType.LastEditedBy, label: "table.field.lastEditedBy" },
]
interface IFieldTypeSelectProps {
@@ -100,6 +50,7 @@ interface IFieldTypeSelectProps {
export function FieldTypeSelect({ value, onChange }: IFieldTypeSelectProps) {
const [open, setOpen] = React.useState(false)
+ const { t } = useTranslation()
const canBeSelected = fields.some((field) => field.value === value)
return (
@@ -115,22 +66,22 @@ export function FieldTypeSelect({ value, onChange }: IFieldTypeSelectProps) {
{value ? (
- {
+ {t(
[...fields, ...readonlyFields].find(
(field) => field.value === value
- )?.label
- }
+ )?.label || ""
+ )}
) : (
- "Select field..."
+ t("table.field.selectField")
)}
-
- No field found.
+
+ {t("table.field.noFieldFound")}
{fields.map((field) => (
- {field.label}
+ {t(field.label)}
))}
diff --git a/lib/env.ts b/lib/env.ts
index ad389b7d..42c324b0 100644
--- a/lib/env.ts
+++ b/lib/env.ts
@@ -1,5 +1,5 @@
export const logger = console
-export const EIDOS_VERSION = "0.7.9"
+export const EIDOS_VERSION = "0.8.0"
export const isDevMode = Boolean(import.meta.env?.DEV)
export const isSelfHosted = import.meta.env?.VITE_EIDOS_SELF_HOSTED === "true"
export const isInkServiceMode =
diff --git a/locales/en.json b/locales/en.json
new file mode 100644
index 00000000..4dd86154
--- /dev/null
+++ b/locales/en.json
@@ -0,0 +1,321 @@
+{
+ "aiChat.inputEditor.pressAtToMentionResource": "@ to mention resource",
+ "aiChat.inputEditor.pressSlashToSwitchPrompt": "Press / to switch prompt",
+ "aiChat.inputEditor.typeYourMessageHere": "Type your message here.",
+ "cmdk.inputPlaceholder": "Type a command or search... (type / for scripts)",
+ "cmdk.newDraftDoc": "New Draft Doc",
+ "cmdk.notFound": "Not found \"{{input}}\"",
+ "cmdk.suggestions": "Suggestions",
+ "cmdk.switchTheme": "Switch Theme",
+ "common.ai": "AI",
+ "common.apiKey": "API Key",
+ "common.areYouAbsolutelySure": "Are you absolutely sure?",
+ "common.cancel": "Cancel",
+ "common.continue": "Continue",
+ "common.delete": "Delete",
+ "common.download": "Download",
+ "common.error": "Error",
+ "common.esc": "ESC",
+ "common.export": "Export",
+ "common.extensions": "Extensions",
+ "common.fetch": "Fetch",
+ "common.files": "Files",
+ "common.filter": "Filter...",
+ "common.import": "Import",
+ "common.importFileDescription": "Choose a file to import into your workspace.",
+ "common.lock": "Locked",
+ "common.name": "Name",
+ "common.new": "New",
+ "common.noResultsFound": "No results found",
+ "common.noTableFound": "No table found.",
+ "common.nodes": "Nodes",
+ "common.open": "Open",
+ "common.paste": "Paste",
+ "common.pinned": "Pinned",
+ "common.remove": "Remove",
+ "common.restore": "Restore",
+ "common.search": "Search",
+ "common.select": "Select",
+ "common.settings": "Settings",
+ "common.shareMode": "Share Mode",
+ "common.system": "System",
+ "common.test": "Test",
+ "common.thisActionCannotBeUndone": "This action cannot be undone.",
+ "common.today": "Today",
+ "common.trash": "Trash",
+ "common.update": "Update",
+ "doc.addCover": "Add Cover",
+ "doc.addIcon": "Add Icon",
+ "doc.changeCover": "Change cover",
+ "doc.coverImage": "Cover image",
+ "doc.hideProperties": "Hide Properties",
+ "doc.menu.background": "Background {{name}}",
+ "doc.menu.bookmark": "Bookmark",
+ "doc.menu.bulletedList": "Bulleted List",
+ "doc.menu.checkList": "Check List",
+ "doc.menu.code": "Code",
+ "doc.menu.color": "Color {{name}}",
+ "doc.menu.databaseTable": "Database Table",
+ "doc.menu.divider": "Divider",
+ "doc.menu.heading": "Heading {{n}}",
+ "doc.menu.image": "Image",
+ "doc.menu.insertTable": "{{rows}}x{{columns}} Table",
+ "doc.menu.numberedList": "Numbered List",
+ "doc.menu.paragraph": "Paragraph",
+ "doc.menu.query": "Query",
+ "doc.menu.quote": "Quote",
+ "doc.menu.tableOfContent": "Table Of Content",
+ "doc.nodeInTrash": "This node is in the trash",
+ "doc.permanentDeleteWarning": "This action cannot be undone. This will permanently delete the node",
+ "doc.pressForCommand": "press / for Command",
+ "doc.showProperties": "Show Properties",
+ "doc.untitled": "Untitled",
+ "everyday.import.importing": "Importing...",
+ "everyday.import.selectFolder": "Click here to select Logseq journal folder",
+ "everyday.import.title": "Import From Logseq (Beta)",
+ "kbd.shortcuts.common.description": "Description",
+ "kbd.shortcuts.common.openSettingsDescription": "Opens the settings menu",
+ "kbd.shortcuts.common.shortcut": "Shortcut",
+ "kbd.shortcuts.common.title": "Common Keyboard Shortcuts",
+ "kbd.shortcuts.common.toggleChatbotDescription": "Opens or closes the chatbot interface",
+ "kbd.shortcuts.common.toggleCommandPaletteDescription": "Opens or closes the command palette",
+ "kbd.shortcuts.common.toggleSidebarDescription": "Shows or hides the sidebar",
+ "kbd.shortcuts.common.toggleThemeDescription": "Switches between light and dark themes",
+ "kbd.shortcuts.document.checkboxDescription": "Creates a checkbox",
+ "kbd.shortcuts.document.codeBlockDescription": "Creates a code block",
+ "kbd.shortcuts.document.ctrlBDescription": "Toggles bold formatting for selected text",
+ "kbd.shortcuts.document.ctrlIDescription": "Toggles italic formatting for selected text",
+ "kbd.shortcuts.document.ctrlSDescription": "Saves the current document",
+ "kbd.shortcuts.document.ctrlUDescription": "Toggles underline formatting for selected text",
+ "kbd.shortcuts.document.heading1Description": "Creates a level 1 heading",
+ "kbd.shortcuts.document.heading2Description": "Creates a level 2 heading",
+ "kbd.shortcuts.document.heading3Description": "Creates a level 3 heading",
+ "kbd.shortcuts.document.horizontalRuleDescription": "Inserts a horizontal rule",
+ "kbd.shortcuts.document.orderedListDescription": "Creates an ordered list item",
+ "kbd.shortcuts.document.title": "Document Keyboard Shortcuts",
+ "kbd.shortcuts.document.unorderedListDescription": "Creates an unordered list item",
+ "kbd.shortcuts.table.altArrowDescription": "Moves the currently selected cell and retains the current selection",
+ "kbd.shortcuts.table.arrowDescription": "Moves the currently selected cell and clears other selections",
+ "kbd.shortcuts.table.ctrlADescription": "Selects the entire table",
+ "kbd.shortcuts.table.ctrlArrowDescription": "Moves to the edge of the current data region in the direction pressed",
+ "kbd.shortcuts.table.ctrlCDescription": "Copies the selected cells",
+ "kbd.shortcuts.table.ctrlDDescription": "Fills the selected cells with the value of the topmost selected cell",
+ "kbd.shortcuts.table.ctrlFDescription": "Opens the search dialog",
+ "kbd.shortcuts.table.ctrlHomeEndDescription": "Moves to the first or last cell of the table",
+ "kbd.shortcuts.table.ctrlRDescription": "Fills the selected cells with the value of the leftmost selected cell",
+ "kbd.shortcuts.table.ctrlShiftArrowDescription": "Extends the current selection to the edge of the current data region in the direction pressed",
+ "kbd.shortcuts.table.ctrlShiftHomeEndDescription": "Selects from the current cell to the first or last cell of the table",
+ "kbd.shortcuts.table.ctrlSpaceDescription": "Selects the entire column",
+ "kbd.shortcuts.table.ctrlVDescription": "Pastes the copied cells",
+ "kbd.shortcuts.table.escapeDescription": "Deselects all selected cells",
+ "kbd.shortcuts.table.pageUpDownDescription": "Moves one page up or down",
+ "kbd.shortcuts.table.shiftArrowDescription": "Extends the current selection range in the direction pressed",
+ "kbd.shortcuts.table.shiftHomeEndDescription": "Extends the selection to the beginning or end of the current row",
+ "kbd.shortcuts.table.shiftSpaceDescription": "Selects the entire row",
+ "kbd.shortcuts.table.title": "Table(Grid View) Keyboard Shortcuts",
+ "nav.dropdown.menu.commandPalette": "Command Palette",
+ "nav.dropdown.menu.creatingEmbedding": "Creating Embedding for {{name}}",
+ "nav.dropdown.menu.desktop": "Desktop",
+ "nav.dropdown.menu.emailInstructions": "1. Scan the QR code to add the address to your contacts\n2. Send an email to this address to save data into this table",
+ "nav.dropdown.menu.embedding": "Embedding(Beta)",
+ "nav.dropdown.menu.embeddingCreated": "Embedding Created",
+ "nav.dropdown.menu.fullWidth": "Full Width",
+ "nav.dropdown.menu.keyboardShortcuts": "Keyboard Shortcuts",
+ "nav.dropdown.menu.lastUpdated": "Last updated: {{time}}",
+ "nav.dropdown.menu.lock": "Lock",
+ "nav.dropdown.menu.mail": "Mail",
+ "nav.dropdown.menu.sendMailToEidos": "Send mail to Eidos",
+ "nav.dropdown.menu.version": "Version: {{version}} ({{mode}})",
+ "nav.dropdown.menu.web": "Web",
+ "nav.dropdown.menu.website": "Website",
+ "nav.status.apiAgentConnected": "API Agent Connected",
+ "nav.status.checkForUpdates": "Check for updates",
+ "nav.status.clickToPin": "Click to pin this node",
+ "nav.status.clickToUnpin": "Click to unpin this node",
+ "nav.status.noApiAgentConnected": "No API Agent Connected",
+ "nav.status.noUpdatesAvailable": "No updates available",
+ "nav.status.nodeLocked": "This node is locked (read-only)",
+ "nav.status.updateAvailable": "Update to v{{version}}",
+ "node.menu.cancelCut": "Cancel cut",
+ "node.menu.cut": "Cut",
+ "node.menu.duplicate": "Duplicate",
+ "node.menu.moveInto": "Move Into",
+ "node.menu.newDoc": "New Doc",
+ "node.menu.newFolder": "New Folder",
+ "node.menu.newNestedFolder": "New Nested Folder",
+ "node.menu.newTable": "New Table",
+ "node.menu.pin": "Pin",
+ "node.menu.rename": "Rename",
+ "node.menu.unpin": "Unpin",
+ "settings.ai": "AI",
+ "settings.ai.addProvider": "Add Provider",
+ "settings.ai.addProviderDescription": "Add a new LLM provider to your configuration.",
+ "settings.ai.apiKeyDescription": "Enter your API key for authentication.",
+ "settings.ai.baseUrl": "Base URL",
+ "settings.ai.baseUrlDescription": "The base URL for the API.",
+ "settings.ai.baseUrlRequired": "Base URL is required.",
+ "settings.ai.codingModel": "Coding Model",
+ "settings.ai.codingModelDescription": "Select your preferred model for coding tasks",
+ "settings.ai.configUpdated": "AI Config updated.",
+ "settings.ai.description": "Configure your AI settings.",
+ "settings.ai.embeddingModel": "Embedding Model",
+ "settings.ai.embeddingModelDescription": "Select your preferred model for embedding tasks",
+ "settings.ai.fetchModelListError": "Failed to fetch model list.",
+ "settings.ai.localLLMDescription": "Manage your local LLM.",
+ "settings.ai.localLLMTitle": "Local LLM",
+ "settings.ai.modelPreferences": "Model Preferences",
+ "settings.ai.modelPreferencesDescription": "Select preferred models for different tasks",
+ "settings.ai.models": "Models",
+ "settings.ai.modelsDescription": "Comma-separated list of model IDs.",
+ "settings.ai.provider": "Provider",
+ "settings.ai.providerDescription": "There are many LLM API providers. configure as your need.",
+ "settings.ai.providerType": "Provider Type",
+ "settings.ai.translationModel": "Translation Model",
+ "settings.ai.translationModelDescription": "Select your preferred model for translation tasks",
+ "settings.ai.updateLLMProvider": "Update LLM Provider",
+ "settings.api": "API",
+ "settings.api.agentUrl": "API Agent URL",
+ "settings.api.agentUrlDescription": "The URL of your API Agent.",
+ "settings.api.callApiThrough": "Call API through",
+ "settings.api.description": "Configure your API settings.",
+ "settings.api.enable": "Enable",
+ "settings.api.enableDescription": "When enabled, you can query data from Eidos Web APP through",
+ "settings.api.regenerate": "Regenerate",
+ "settings.api.update": "Update",
+ "settings.appearance": "Appearance",
+ "settings.appearance.dark": "Dark",
+ "settings.appearance.description": "Customize the appearance of the app. Automatically switch between day and night themes.",
+ "settings.appearance.font": "Font",
+ "settings.appearance.fontDescription": "Set the font you want to use in the dashboard.",
+ "settings.appearance.language": "Language",
+ "settings.appearance.languageDescription": "Select your preferred language.",
+ "settings.appearance.light": "Light",
+ "settings.appearance.submittedValues": "You submitted the following values:",
+ "settings.appearance.theme": "Theme",
+ "settings.appearance.themeDescription": "Select your preferred theme.",
+ "settings.appearance.title": "Appearance",
+ "settings.appearance.updatePreferences": "Update preferences",
+ "settings.devtools": "Devtools",
+ "settings.devtools.description": "Developer tools settings.",
+ "settings.experiment": "Experiment",
+ "settings.experiment.description": "Experimental features settings.",
+ "settings.general": "General",
+ "settings.general.clientId": "Client ID",
+ "settings.general.description": "How others will see you when collaborating.",
+ "settings.manageAppSettings": "Manage App Settings and Configuration",
+ "settings.security": "Security",
+ "settings.security.description": "Configure your security settings.",
+ "settings.storage": "Storage",
+ "settings.storage.autoBackupDescription": "Backup data every {{minutes}} minutes, 0 means disable auto save.",
+ "settings.storage.autoBackupDisabled": "Disable auto save.",
+ "settings.storage.autoBackupExplanation": "Backup every space's database to the local path. Keep data more secure.",
+ "settings.storage.autoBackupGap": "Auto backup gap (minutes)",
+ "settings.storage.dataFolder": "Data Folder",
+ "settings.storage.dataFolderDescription": "The folder where your data will be stored.",
+ "settings.storage.dataFolderNotSelected": "Data folder not selected",
+ "settings.storage.description": "Configure your storage settings.",
+ "settings.storage.fileSystem": "File System",
+ "settings.storage.fileSystemDescription": "Which file system to store your files. OPFS stores files in the browser's storage, while Native File System stores files in a local directory on your device.",
+ "settings.storage.grantPermission": "Grant Permission",
+ "settings.storage.nativeFileSystem": "Native File System",
+ "settings.storage.noTypeFound": "No type found.",
+ "settings.storage.permissionDenied": "Permission denied.",
+ "settings.storage.permissionGranted": "Permission granted.",
+ "settings.storage.searchType": "Search type...",
+ "settings.storage.selectDataFolder": "You need to select a data folder.",
+ "settings.storage.selectDataFolderPlaceholder": "Select a data folder",
+ "settings.storage.selectFileSystem": "Select File System",
+ "settings.storage.settingsUpdated": "Settings updated",
+ "settings.sync": "Sync",
+ "settings.sync.description": "Configure your sync settings.",
+ "settings.title": "Settings",
+ "sidebar.importFile.description": "Choose a file to import into your workspace.",
+ "sidebar.importFile.importCSV": "Import CSV",
+ "sidebar.importFile.importCSVDescription": "Import a CSV file to create a new table",
+ "sidebar.importFile.importMarkdown": "Import Markdown",
+ "sidebar.importFile.importMarkdownDescription": "Import a markdown file to create a new document",
+ "sidebar.importFile.title": "Import File",
+ "sidebar.trash.restoreOrPermanentlyDeleteNodes": "Restore or permanently delete nodes",
+ "sidebar.trash.thisActionCannotBeUndone": "This action cannot be undone. This will permanently delete the selected node.",
+ "sidebar.trash.trashIsEmpty": "Trash is empty",
+ "space.select.createNew": "Create New",
+ "space.select.createSpace": "Create Space",
+ "space.select.createSpaceDescription": "Add a new space to manage data for you",
+ "space.select.creating": "Creating",
+ "space.select.importFromFile": "Import from file",
+ "space.select.importFromFileDescription": "If you export space as a zip file, you can import it here",
+ "space.select.overwriteWarning": "It seems you are trying to overwrite an existing space. Please be careful, this will overwrite data in the existing space.",
+ "space.select.searchDatabase": "Search Database...",
+ "space.select.selectDatabase": "Select Database...",
+ "space.select.spaceAlreadyExists": "This space already exists, choose another name",
+ "space.select.spaceName": "Space name",
+ "space.select.spaceNamePlaceholder": "e.g. personal",
+ "space.settings.dangerZone": "Danger zone",
+ "space.settings.deleteSpace": "Delete Space",
+ "space.settings.deleteSpaceDescription": "Permanently delete this space and all its contents. This action cannot be undone.",
+ "space.settings.deleteSpaceWarning": "This action cannot be undone. This will permanently delete your space <1>{{spaceName}}1>. Please type the space name to confirm.",
+ "space.settings.description": "Settings only apply to this space. If you want to change settings for all spaces, go to",
+ "space.settings.exportDescription": "Export all data from this space for backup or transfer purposes.",
+ "space.settings.exportSpace": "Export Space",
+ "space.settings.globalSettings": "global settings",
+ "space.settings.rebuildIndex": "Rebuild Index",
+ "space.settings.rebuildIndexDescription": "Reconstruct the search index for this space. Use this if you're experiencing search issues.",
+ "space.settings.rebuilding": "Rebuilding...",
+ "space.settings.title": "Space Settings",
+ "space.settings.typeSpaceName": "Type space name",
+ "table.deleteField": "Delete Field",
+ "table.deleteFieldConfirmation": "Are you sure you want to delete this field?",
+ "table.deleteLinkFieldWarning": "This field is a link field. Deleting this field will also delete the paired field. This action cannot be undone.",
+ "table.editProperty": "Edit Property",
+ "table.field.checkbox": "Checkbox",
+ "table.field.createdBy": "Created By",
+ "table.field.createdTime": "Created Time",
+ "table.field.date": "Date",
+ "table.field.deleteConfirmTitle": "Are you sure you want to delete this field?",
+ "table.field.deleteLinkFieldWarning": "This field is a link field. Deleting this field will also delete the paired field. This action cannot be undone.",
+ "table.field.file": "Files",
+ "table.field.formula": "Formula",
+ "table.field.lastEditedBy": "Last Edited By",
+ "table.field.lastEditedTime": "Last Edited Time",
+ "table.field.link": "Link",
+ "table.field.lookup": "Lookup",
+ "table.field.multiSelect": "Multi-select",
+ "table.field.noFieldFound": "No field found.",
+ "table.field.number": "Number",
+ "table.field.rating": "Rating",
+ "table.field.searchField": "Search field",
+ "table.field.select": "Select",
+ "table.field.selectField": "Select Field",
+ "table.field.text": "Text",
+ "table.field.untitledField": "Untitled Field",
+ "table.field.url": "URL",
+ "table.fieldType": "Type",
+ "table.sortAscending": "Sort Ascending",
+ "table.sortDescending": "Sort Descending",
+ "table.view.addFilter": "Add filter",
+ "table.view.addGroupFilter": "Add group filter",
+ "table.view.addSort": "Add sort",
+ "table.view.and": "AND",
+ "table.view.deleteConfirmDescription": "This action cannot be undone. This will permanently delete the view.",
+ "table.view.deleteConfirmTitle": "Are you sure you want to delete this view?",
+ "table.view.deleteFilter": "Delete filter",
+ "table.view.deleteSort": "Delete sort",
+ "table.view.disabledViewTypesWarning": "The disabled view types are not ready for large data",
+ "table.view.docList": "Doc list (beta)",
+ "table.view.edit": "Edit",
+ "table.view.field.addField": "Add Field",
+ "table.view.field.hideAll": "Hide all",
+ "table.view.field.showAll": "Show all",
+ "table.view.gallery": "Gallery",
+ "table.view.gallery.content": "Content",
+ "table.view.gallery.coverPreview": "Cover preview",
+ "table.view.gallery.hideEmptyFields": "Hide empty fields",
+ "table.view.grid": "Grid",
+ "table.view.noFilterRule": "There is no filter rule, add one",
+ "table.view.noSortRule": "There is no sort rule, add one",
+ "table.view.or": "OR",
+ "table.view.query": "Query",
+ "table.view.queryDescription": "SQL query to use for this view.",
+ "table.view.typeDescription": "The type of view to use for this table.",
+ "table.view.where": "Where"
+}
\ No newline at end of file
diff --git a/locales/i18n.ts b/locales/i18n.ts
new file mode 100644
index 00000000..332807a6
--- /dev/null
+++ b/locales/i18n.ts
@@ -0,0 +1,44 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+import Backend from 'i18next-http-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+
+import enTranslations from './en.json';
+import zhTranslations from './zh.json';
+
+const resources = {
+ en: { translation: enTranslations },
+ zh: { translation: zhTranslations }
+};
+
+const getUserLanguage = () => {
+ const appearancePreferences = localStorage.getItem('appearancePreferences');
+ if (appearancePreferences) {
+ const parsedPreferences = JSON.parse(appearancePreferences)
+ return parsedPreferences.language || 'en';
+ }
+ return 'en';
+};
+
+i18n
+ .use(Backend)
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ resources,
+ lng: getUserLanguage(),
+ fallbackLng: 'en',
+ debug: true,
+ interpolation: {
+ escapeValue: false,
+ },
+ }).then(() => {
+ console.log('i18n initialized successfully');
+ console.log('Current language:', i18n.language);
+ }).catch((err) => {
+ console.log('Error initializing i18n:', err);
+ });
+
+
+
+export default i18n;
diff --git a/locales/json.d.ts b/locales/json.d.ts
new file mode 100644
index 00000000..09eae417
--- /dev/null
+++ b/locales/json.d.ts
@@ -0,0 +1,4 @@
+declare module "*.json" {
+ const value: any;
+ export default value;
+}
\ No newline at end of file
diff --git a/locales/zh.json b/locales/zh.json
new file mode 100644
index 00000000..ecfbed96
--- /dev/null
+++ b/locales/zh.json
@@ -0,0 +1,322 @@
+{
+ "aiChat.inputEditor.pressAtToMentionResource": "@ 提及资源",
+ "aiChat.inputEditor.pressSlashToSwitchPrompt": "按 / 切换提示词",
+ "aiChat.inputEditor.typeYourMessageHere": "在此输入您的消息。",
+ "cmdk.inputPlaceholder": "输入命令或搜索...(输入 / 以查看脚本)",
+ "cmdk.newDraftDoc": "新建草稿文档",
+ "cmdk.notFound": "未找到 \"{{input}}\"",
+ "cmdk.suggestions": "建议",
+ "cmdk.switchTheme": "切换主题",
+ "common.ai": "AI",
+ "common.apiKey": "API 密钥",
+ "common.areYouAbsolutelySure": "您确定要这样做吗?",
+ "common.cancel": "取消",
+ "common.continue": "继续",
+ "common.delete": "删除",
+ "common.download": "下载",
+ "common.error": "错误",
+ "common.esc": "ESC",
+ "common.export": "导出",
+ "common.extensions": "扩展",
+ "common.fetch": "获取",
+ "common.files": "文件",
+ "common.filter": "过滤...",
+ "common.import": "导入",
+ "common.importFileDescription": "选择要导入到工作区的文件。",
+ "common.lock": "已锁定",
+ "common.name": "名称",
+ "common.new": "新建",
+ "common.noResultsFound": "未找到结果",
+ "common.noTableFound": "未找到表格。",
+ "common.nodes": "节点",
+ "common.open": "打开",
+ "common.paste": "粘贴",
+ "common.pinned": "置顶",
+ "common.remove": "移除",
+ "common.restore": "恢复",
+ "common.search": "搜索",
+ "common.select": "选择",
+ "common.settings": "设置",
+ "common.shareMode": "共享模式",
+ "common.system": "系统",
+ "common.test": "测试",
+ "common.thisActionCannotBeUndone": "此操作无法撤消。",
+ "common.today": "今天",
+ "common.trash": "回收站",
+ "common.update": "更新",
+ "doc.addCover": "添加封面",
+ "doc.addIcon": "添加图标",
+ "doc.changeCover": "更改封面",
+ "doc.coverImage": "封面图片",
+ "doc.hideProperties": "隐藏属性",
+ "doc.menu.background": "背景 {{name}}",
+ "doc.menu.bookmark": "书签",
+ "doc.menu.bulletedList": "无序列表",
+ "doc.menu.checkList": "检查列表",
+ "doc.menu.code": "代码",
+ "doc.menu.color": "颜色 {{name}}",
+ "doc.menu.databaseTable": "数据库表",
+ "doc.menu.divider": "分割线",
+ "doc.menu.heading": "标题 {{n}}",
+ "doc.menu.image": "图片",
+ "doc.menu.insertTable": "{{rows}}x{{columns}} 表格",
+ "doc.menu.numberedList": "有序列表",
+ "doc.menu.paragraph": "段落",
+ "doc.menu.query": "查询",
+ "doc.menu.quote": "引用",
+ "doc.menu.tableOfContent": "目录",
+ "doc.nodeInTrash": "此节点在回收站中",
+ "doc.permanentDeleteWarning": "此操作无法撤消。这将永久删除该节点",
+ "doc.pressForCommand": "按 / 键输入命令",
+ "doc.showProperties": "显示属性",
+ "doc.untitled": "无标题",
+ "everyday.import.importing": "正在导入...",
+ "everyday.import.selectFolder": "点击此处选择 Logseq 日记文件夹",
+ "everyday.import.title": "从 Logseq 导入(测试版)",
+ "kbd.shortcuts.common.description": "描述",
+ "kbd.shortcuts.common.openSettingsDescription": "打开设置菜单",
+ "kbd.shortcuts.common.shortcut": "快捷键",
+ "kbd.shortcuts.common.title": "常用键盘快捷键",
+ "kbd.shortcuts.common.toggleChatbotDescription": "打开或关闭聊天机器人界面",
+ "kbd.shortcuts.common.toggleCommandPaletteDescription": "打开或关闭命令面板",
+ "kbd.shortcuts.common.toggleSidebarDescription": "显示或隐藏侧边栏",
+ "kbd.shortcuts.common.toggleThemeDescription": "在亮色和暗色主题之间切换",
+ "kbd.shortcuts.document.checkboxDescription": "创建复选框",
+ "kbd.shortcuts.document.codeBlockDescription": "创建代码块",
+ "kbd.shortcuts.document.ctrlBDescription": "切换选中文本的粗体格式",
+ "kbd.shortcuts.document.ctrlIDescription": "切换选中文本的斜体格式",
+ "kbd.shortcuts.document.ctrlSDescription": "保存当前文档",
+ "kbd.shortcuts.document.ctrlUDescription": "切换选中文本的下划线格式",
+ "kbd.shortcuts.document.heading1Description": "创建一级标题",
+ "kbd.shortcuts.document.heading2Description": "创建二级标题",
+ "kbd.shortcuts.document.heading3Description": "创建三级标题",
+ "kbd.shortcuts.document.horizontalRuleDescription": "插入水平分割线",
+ "kbd.shortcuts.document.orderedListDescription": "创建有序列表项",
+ "kbd.shortcuts.document.title": "文档键盘快捷键",
+ "kbd.shortcuts.document.unorderedListDescription": "创建无序列表项",
+ "kbd.shortcuts.table.altArrowDescription": "移动当前选中的单元格并保留当前选择",
+ "kbd.shortcuts.table.arrowDescription": "移动当前选中的单元格并清除其他选择",
+ "kbd.shortcuts.table.ctrlADescription": "选择整个表格",
+ "kbd.shortcuts.table.ctrlArrowDescription": "移动到当前数据区域在按下方向上的边缘",
+ "kbd.shortcuts.table.ctrlCDescription": "复制选中的单元格",
+ "kbd.shortcuts.table.ctrlDDescription": "用最上方选中单元格的值填充选中的单元格",
+ "kbd.shortcuts.table.ctrlFDescription": "打开搜索对话框",
+ "kbd.shortcuts.table.ctrlHomeEndDescription": "移动到表格的第一个或最后一个单元格",
+ "kbd.shortcuts.table.ctrlRDescription": "用最左侧选中单元格的值填充选中的单元格",
+ "kbd.shortcuts.table.ctrlShiftArrowDescription": "将当前选择扩展到当前数据区域在按下方向上的边缘",
+ "kbd.shortcuts.table.ctrlShiftHomeEndDescription": "从当前单元格选择到表格的第一个或最后一个单元格",
+ "kbd.shortcuts.table.ctrlSpaceDescription": "选择整列",
+ "kbd.shortcuts.table.ctrlVDescription": "粘贴复制的单元格",
+ "kbd.shortcuts.table.escapeDescription": "取消选择所有选中的单元格",
+ "kbd.shortcuts.table.pageUpDownDescription": "上下翻页",
+ "kbd.shortcuts.table.shiftArrowDescription": "向按下的方向扩展当前选择范围",
+ "kbd.shortcuts.table.shiftHomeEndDescription": "将选择扩展到当前行的开始或结束",
+ "kbd.shortcuts.table.shiftSpaceDescription": "选择整行",
+ "kbd.shortcuts.table.title": "表格(网格视图)键盘快捷键",
+ "nav.dropdown.menu.commandPalette": "命令面板",
+ "nav.dropdown.menu.creatingEmbedding": "正在为 {{name}} 创建嵌入",
+ "nav.dropdown.menu.desktop": "桌面版",
+ "nav.dropdown.menu.emailInstructions": "1. 扫描二维码将地址添加到您的联系人\n2. 发送邮件到此地址以将数据保存到此表格",
+ "nav.dropdown.menu.embedding": "嵌入(测试版)",
+ "nav.dropdown.menu.embeddingCreated": "嵌入已创建",
+ "nav.dropdown.menu.fullWidth": "全宽",
+ "nav.dropdown.menu.keyboardShortcuts": "键盘快捷键",
+ "nav.dropdown.menu.lastUpdated": "最后更新:{{time}}",
+ "nav.dropdown.menu.lock": "锁定",
+ "nav.dropdown.menu.mail": "邮件",
+ "nav.dropdown.menu.sendMailToEidos": "发送邮件到 Eidos",
+ "nav.dropdown.menu.version": "版本:{{version}}({{mode}})",
+ "nav.dropdown.menu.web": "网页版",
+ "nav.dropdown.menu.website": "网站首页",
+ "nav.status.apiAgentConnected": "API 代理已连接",
+ "nav.status.checkForUpdates": "检查更新",
+ "nav.status.clickToPin": "点击固定此节点",
+ "nav.status.clickToUnpin": "点击取消固定此节点",
+ "nav.status.noApiAgentConnected": "未连接 API 代理",
+ "nav.status.noUpdatesAvailable": "无可用更新",
+ "nav.status.nodeLocked": "此节点已锁定(只读)",
+ "nav.status.updateAvailable": "更新到 v{{version}}",
+ "node.menu.cancelCut": "取消剪切",
+ "node.menu.cut": "剪切",
+ "node.menu.duplicate": "复制",
+ "node.menu.moveInto": "移动到",
+ "node.menu.newDoc": "新建文档",
+ "node.menu.newFolder": "新建文件夹",
+ "node.menu.newNestedFolder": "新建嵌套文件夹",
+ "node.menu.newTable": "新建表格",
+ "node.menu.pin": "固定",
+ "node.menu.rename": "重命名",
+ "node.menu.unpin": "取消固定",
+ "settings.ai": "AI",
+ "settings.ai.addProvider": "添加提供者",
+ "settings.ai.addProviderDescription": "将新的 LLM 提供者添加到您的配置中。",
+ "settings.ai.apiKeyDescription": "输入您的 API 密钥以进行身份验证。",
+ "settings.ai.baseUrl": "基础 URL",
+ "settings.ai.baseUrlDescription": "API 的基础 URL。",
+ "settings.ai.baseUrlRequired": "基础 URL 是必需的。",
+ "settings.ai.codingModel": "编码模型",
+ "settings.ai.codingModelDescription": "选择您偏好的编码任务模型",
+ "settings.ai.configUpdated": "AI 配置已更新。",
+ "settings.ai.description": "配置您的 AI 设置。",
+ "settings.ai.embeddingModel": "嵌入模型",
+ "settings.ai.embeddingModelDescription": "选择您偏好的嵌入任务模型",
+ "settings.ai.fetchModelListError": "获取模型列表失败。",
+ "settings.ai.localLLMDescription": "管理您的本地 LLM。",
+ "settings.ai.localLLMTitle": "本地 LLM",
+ "settings.ai.modelPreferences": "模型偏好",
+ "settings.ai.modelPreferencesDescription": "为不同任务选择偏好的模型",
+ "settings.ai.models": "模型",
+ "settings.ai.modelsDescription": "以逗号分隔的模型 ID 列表。",
+ "settings.ai.provider": "提供者",
+ "settings.ai.providerDescription": "有许多 LLM API 提供者。根据您的需要进行配置。",
+ "settings.ai.providerType": "提供者类型",
+ "settings.ai.translationModel": "翻译模型",
+ "settings.ai.translationModelDescription": "选择您偏好的翻译任务模型",
+ "settings.ai.updateLLMProvider": "更新 LLM 提供者",
+ "settings.api": "API",
+ "settings.api.agentUrl": "API 代理 URL",
+ "settings.api.agentUrlDescription": "您的 API 代理的 URL。",
+ "settings.api.callApiThrough": "通过以下方式调用 API",
+ "settings.api.description": "配置您的 API 设置。",
+ "settings.api.enable": "启用",
+ "settings.api.enableDescription": "启用后,您可以通过 API 代理从 Eidos Web APP 查询数据",
+ "settings.api.regenerate": "重新生成",
+ "settings.api.update": "更新",
+ "settings.appearance": "外观",
+ "settings.appearance.dark": "暗色",
+ "settings.appearance.description": "自定义应用的外观。自动在亮色和暗色主题之间切换。",
+ "settings.appearance.font": "字体",
+ "settings.appearance.fontDescription": "设置您想在仪表板中使用的字体。",
+ "settings.appearance.language": "语言",
+ "settings.appearance.languageDescription": "选择您偏好的语言。",
+ "settings.appearance.light": "亮色",
+ "settings.appearance.submittedValues": "您提交了以下值:",
+ "settings.appearance.theme": "主题",
+ "settings.appearance.themeDescription": "选择您偏好的主题。",
+ "settings.appearance.title": "外观",
+ "settings.appearance.updatePreferences": "更新偏好设置",
+ "settings.devtools": "开发工具",
+ "settings.devtools.description": "开发者工具设置。",
+ "settings.experiment": "实验",
+ "settings.experiment.description": "试验性功能设置。",
+ "settings.general": "通用",
+ "settings.general.clientId": "客户端 ID",
+ "settings.general.description": "他人在协作时如何看到您。",
+ "settings.manageAppSettings": "管理应用设置和配置",
+ "settings.security": "安全",
+ "settings.security.description": "配置您的安全设置。",
+ "settings.storage": "存储",
+ "settings.storage.autoBackupDescription": "每 {{minutes}} 分钟备份一次数据,0 表示禁用自动保存。",
+ "settings.storage.autoBackupDisabled": "禁用自动保存。",
+ "settings.storage.autoBackupExplanation": "将每个空间的数据库备份到本地路径。保持数据更安全。",
+ "settings.storage.autoBackupGap": "自动备份间隔(分钟)",
+ "settings.storage.dataFolder": "数据文件夹",
+ "settings.storage.dataFolderDescription": "您的数据将存储在此文件夹中。",
+ "settings.storage.dataFolderNotSelected": "未选择数据文件夹",
+ "settings.storage.description": "配置您的存储设置。",
+ "settings.storage.fileSystem": "文件系统",
+ "settings.storage.fileSystemDescription": "选择用于存储文件的文件系统。OPFS 将文件存储在浏览器的存储中,而本地文件系统将文件存储在您设备上的地目录中。",
+ "settings.storage.grantPermission": "授予权限",
+ "settings.storage.nativeFileSystem": "本地文件系统",
+ "settings.storage.noTypeFound": "未找到类型。",
+ "settings.storage.permissionDenied": "权限被拒绝。",
+ "settings.storage.permissionGranted": "已授予权限。",
+ "settings.storage.searchType": "搜索类型...",
+ "settings.storage.selectDataFolder": "您需要选择一个数据文件夹。",
+ "settings.storage.selectDataFolderPlaceholder": "选择数据文件夹",
+ "settings.storage.selectFileSystem": "选择文件系统",
+ "settings.storage.settingsUpdated": "设置已更新",
+ "settings.sync": "同步",
+ "settings.sync.description": "配置您的同步设置。",
+ "settings.title": "设置",
+ "sidebar.importFile.description": "选择要导入到工作区的文件。",
+ "sidebar.importFile.importCSV": "导入 CSV",
+ "sidebar.importFile.importCSVDescription": "从 CSV 文件导入表格数据。",
+ "sidebar.importFile.importMarkdown": "导入 Markdown",
+ "sidebar.importFile.importMarkdownDescription": "从 Markdown 文件导入文档。",
+ "sidebar.importFile.title": "导入文件",
+ "sidebar.trash.restoreOrPermanentlyDeleteNodes": "恢复或永久删除节点",
+ "sidebar.trash.thisActionCannotBeUndone": "此操作无法撤消。这将永久删除所选节点。",
+ "sidebar.trash.trashIsEmpty": "回收站为空",
+ "space.select.createNew": "创建新数据库",
+ "space.select.createSpace": "创建空间",
+ "space.select.createSpaceDescription": "添加一个新空间以管理数据",
+ "space.select.creating": "正在创建",
+ "space.select.importFromFile": "从文件导入",
+ "space.select.importFromFileDescription": "如果您将空间导出为 zip 文件,您可以在此处导入它",
+ "space.select.overwriteWarning": "似乎您正在尝试覆盖现有空间。请小心,这将覆盖现有空间中的数据。",
+ "space.select.searchDatabase": "搜索数据库...",
+ "space.select.selectDatabase": "选择数据库...",
+ "space.select.spaceAlreadyExists": "此空间已存在,请选择另一个名称",
+ "space.select.spaceName": "空间名称",
+ "space.select.spaceNamePlaceholder": "例如:personal",
+ "space.settings.dangerZone": "危险区域",
+ "space.settings.deleteSpace": "删除空间",
+ "space.settings.deleteSpaceDescription": "永久删除此空间及其所有内容。此操作无法撤消。",
+ "space.settings.deleteSpaceWarning": "此操作无法撤消。这将永久删除您的空间 <1>{{spaceName}}1>。请输入空间名称以确认。",
+ "space.settings.description": "这些设置仅适用于当前空间。如果您想更改所有空间的设置,请前往",
+ "space.settings.exportDescription": "导出此空间的所有数据以进行备份或传输。",
+ "space.settings.exportSpace": "导出空间",
+ "space.settings.globalSettings": "全局设置",
+ "space.settings.rebuildIndex": "重建索引",
+ "space.settings.rebuildIndexDescription": "重新构建此空间的搜索索引。如果您遇到搜索问题,请使用此功能。",
+ "space.settings.rebuilding": "正在重建...",
+ "space.settings.title": "空间设置",
+ "space.settings.typeSpaceName": "输入空间名称",
+ "table.deleteField": "删除字段",
+ "table.deleteFieldConfirmation": "您确定要删除此字段吗?",
+ "table.deleteLinkFieldWarning": "此字段是链接字段。删除此字段也会删除配对字段。此操作无法撤消。",
+ "table.editProperty": "编辑属性",
+ "table.field.checkbox": "复选框",
+ "table.field.createdBy": "创建者",
+ "table.field.createdTime": "创建时间",
+ "table.field.date": "日期",
+ "table.field.deleteConfirmTitle": "您确定要删除此字段吗?",
+ "table.field.deleteLinkFieldWarning": "此字段是链接字段。删除此字段也会删除配对字段。此操作无法撤消。",
+ "table.field.file": "文件",
+ "table.field.formula": "公式",
+ "table.field.lastEditedBy": "最后编辑者",
+ "table.field.lastEditedTime": "最后编辑时间",
+ "table.field.link": "关联",
+ "table.field.lookup": "查找",
+ "table.field.multiSelect": "多选",
+ "table.field.noFieldFound": "未找到字段。",
+ "table.field.number": "数字",
+ "table.field.rating": "评分",
+ "table.field.searchField": "搜索字段",
+ "table.field.select": "单选",
+ "table.field.selectField": "选择字段",
+ "table.field.text": "文本",
+ "table.field.title": "标题",
+ "table.field.untitledField": "未命名字段",
+ "table.field.url": "网址",
+ "table.fieldType": "类型",
+ "table.sortAscending": "升序",
+ "table.sortDescending": "降序",
+ "table.view.addFilter": "添加筛选",
+ "table.view.addGroupFilter": "添加分组筛选",
+ "table.view.addSort": "添加排序",
+ "table.view.and": "并且",
+ "table.view.deleteConfirmDescription": "此操作无法撤消。这将永久删除该视图。",
+ "table.view.deleteConfirmTitle": "您确定要删除此视图吗?",
+ "table.view.deleteFilter": "删除筛选",
+ "table.view.deleteSort": "删除排序",
+ "table.view.disabledViewTypesWarning": "禁用的视图类型不适用于大量数据",
+ "table.view.docList": "文档列表(测试版)",
+ "table.view.edit": "编辑",
+ "table.view.field.addField": "添加字段",
+ "table.view.field.hideAll": "隐藏全部",
+ "table.view.field.showAll": "显示全部",
+ "table.view.gallery": "画廊",
+ "table.view.gallery.content": "内容",
+ "table.view.gallery.coverPreview": "封面预览",
+ "table.view.gallery.hideEmptyFields": "隐藏空字段",
+ "table.view.grid": "网格",
+ "table.view.noFilterRule": "没有筛选规则,添加一个",
+ "table.view.noSortRule": "没有排序规则,添加一个",
+ "table.view.or": "或者",
+ "table.view.query": "查询",
+ "table.view.queryDescription": "用于此视图的 SQL 查询。",
+ "table.view.typeDescription": "用于此表格的视图类型。",
+ "table.view.where": "当"
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 40e403e3..dd3e2f2d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eidos",
- "version": "0.7.9",
+ "version": "0.8.0",
"private": true,
"type": "module",
"description": "Eidos is an extensible framework for managing your personal data throughout your lifetime in one place.",
@@ -175,6 +175,9 @@
"hnswlib-wasm": "^0.8.2",
"hono": "^4.5.11",
"html2canvas": "^1.4.1",
+ "i18next": "^23.16.2",
+ "i18next-browser-languagedetector": "^8.0.0",
+ "i18next-http-backend": "^2.6.2",
"idb-keyval": "^6.2.1",
"immutability-helper": "^3.1.1",
"jszip": "^3.10.1",
@@ -204,6 +207,7 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.1",
+ "react-i18next": "^15.1.0",
"react-infinite-scroll-hook": "^4.1.1",
"react-laag": "^2.0.5",
"react-marked-renderer": "^1.1.2",
@@ -299,4 +303,4 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a01ac19d..a52d3ff4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -263,6 +263,15 @@ importers:
html2canvas:
specifier: ^1.4.1
version: 1.4.1
+ i18next:
+ specifier: ^23.16.2
+ version: 23.16.2
+ i18next-browser-languagedetector:
+ specifier: ^8.0.0
+ version: 8.0.0
+ i18next-http-backend:
+ specifier: ^2.6.2
+ version: 2.6.2
idb-keyval:
specifier: ^6.2.1
version: 6.2.1
@@ -350,6 +359,9 @@ importers:
react-hook-form:
specifier: ^7.45.1
version: 7.45.1(react@18.2.0)
+ react-i18next:
+ specifier: ^15.1.0
+ version: 15.1.0(i18next@23.16.2)(react-dom@18.2.0)(react@18.2.0)
react-infinite-scroll-hook:
specifier: ^4.1.1
version: 4.1.1(react@18.2.0)
@@ -4546,13 +4558,13 @@ packages:
/@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
dev: false
/@radix-ui/primitive@1.0.0:
resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==}
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
dev: false
/@radix-ui/primitive@1.0.1:
@@ -4633,7 +4645,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
@@ -4798,7 +4810,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
react: 18.2.0
dev: false
@@ -4860,7 +4872,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
react: 18.2.0
dev: false
@@ -4897,7 +4909,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
'@radix-ui/react-context': 1.0.0(react@18.2.0)
@@ -4972,7 +4984,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.0
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
@@ -4995,7 +5007,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@@ -5020,7 +5032,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@@ -5064,7 +5076,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
react: 18.2.0
dev: false
@@ -5077,7 +5089,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@types/react': 18.2.14
react: 18.2.0
dev: false
@@ -5088,7 +5100,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
@@ -5109,7 +5121,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
@@ -5132,7 +5144,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0)
react: 18.2.0
dev: false
@@ -5200,7 +5212,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
@@ -5273,7 +5285,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
@@ -5303,7 +5315,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
@@ -5326,7 +5338,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -5345,7 +5357,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
@@ -5366,7 +5378,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
@@ -5380,7 +5392,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0)
react: 18.2.0
@@ -5436,7 +5448,7 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-slot': 1.0.0(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -5548,7 +5560,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0)
@@ -5691,7 +5703,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
react: 18.2.0
dev: false
@@ -5872,7 +5884,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
react: 18.2.0
dev: false
@@ -5908,7 +5920,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
react: 18.2.0
dev: false
@@ -5947,7 +5959,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
react: 18.2.0
dev: false
@@ -5961,7 +5973,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0)
'@types/react': 18.2.14
react: 18.2.0
@@ -5972,7 +5984,7 @@ packages:
peerDependencies:
react: ^16.8 || ^17.0 || ^18.0
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
react: 18.2.0
dev: false
@@ -6026,7 +6038,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.14
react: 18.2.0
@@ -6060,7 +6072,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 18.2.14
'@types/react-dom': 18.2.6
@@ -6071,7 +6083,7 @@ packages:
/@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
dev: false
/@react-dnd/asap@5.0.2:
@@ -8615,6 +8627,14 @@ packages:
cross-spawn: 7.0.3
dev: true
+ /cross-fetch@4.0.0:
+ resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
+ dependencies:
+ node-fetch: 2.6.13
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@@ -10731,6 +10751,12 @@ packages:
whatwg-encoding: 3.1.1
dev: true
+ /html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+ dependencies:
+ void-elements: 3.1.0
+ dev: false
+
/html-rewriter-wasm@0.4.1:
resolution: {integrity: sha512-lNovG8CMCCmcVB1Q7xggMSf7tqPCijZXaH4gL6iE8BFghdQCbaY5Met9i1x2Ex8m/cZHDUtXK9H6/znKamRP8Q==}
dev: true
@@ -10823,6 +10849,26 @@ packages:
ms: 2.1.3
dev: false
+ /i18next-browser-languagedetector@8.0.0:
+ resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
+ dependencies:
+ '@babel/runtime': 7.25.7
+ dev: false
+
+ /i18next-http-backend@2.6.2:
+ resolution: {integrity: sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==}
+ dependencies:
+ cross-fetch: 4.0.0
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
+ /i18next@23.16.2:
+ resolution: {integrity: sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==}
+ dependencies:
+ '@babel/runtime': 7.25.7
+ dev: false
+
/iconv-corefoundation@1.1.7:
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
engines: {node: ^8.11.2 || >=10}
@@ -13095,6 +13141,26 @@ packages:
html-element-attributes: 1.3.1
dev: false
+ /react-i18next@15.1.0(i18next@23.16.2)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-zj3nJynMnZsy2gPZiOTC7XctCY5eQGqT3tcKMmfJWC9FMvgd+960w/adq61j8iPzpwmsXejqID9qC3Mqu1Xu2Q==}
+ peerDependencies:
+ i18next: '>= 23.2.3'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.25.7
+ html-parse-stringify: 3.0.1
+ i18next: 23.16.2
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/react-infinite-scroll-hook@4.1.1(react@18.2.0):
resolution: {integrity: sha512-1bu2572rF3DtjFMhIOzoasLMdYW0vMWxROtl99M5FYGSxm84Ro4aNBZW6ivgE45ofus4Ymo7jIS0Be3zcuLk8g==}
engines: {node: '>=10'}
@@ -13373,7 +13439,7 @@ packages:
/redux@4.2.1:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
dependencies:
- '@babel/runtime': 7.24.5
+ '@babel/runtime': 7.25.7
dev: false
/regenerate-unicode-properties@10.2.0:
@@ -15194,6 +15260,11 @@ packages:
- terser
dev: true
+ /void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
/w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}