diff --git a/web/src/components/ActivityCalendar.tsx b/web/src/components/ActivityCalendar.tsx index 03978fd6d..9443dd3c9 100644 --- a/web/src/components/ActivityCalendar.tsx +++ b/web/src/components/ActivityCalendar.tsx @@ -74,7 +74,14 @@ const ActivityCalendar = (props: Props) => { const date = dayjs(`${year}-${month + 1}-${item.day}`).format("YYYY-MM-DD"); const count = item.isCurrentMonth ? data[date] || 0 : 0; const isToday = dayjs().format("YYYY-MM-DD") === date; - const tooltipText = count ? t("memo.count-memos-in-date", { count: count, date: date }) : date; + const tooltipText = + count === 0 + ? t("memo.no-memos") + : t("memo.count-memos-in-date", { + count: count, + memos: count === 1 ? t("common.memo") : t("common.memos"), + date: date, + }).toLowerCase(); const isSelected = dayjs(props.selectedDate).format("YYYY-MM-DD") === date; return ( diff --git a/web/src/components/CreateAccessTokenDialog.tsx b/web/src/components/CreateAccessTokenDialog.tsx index e46cec33e..50600591d 100644 --- a/web/src/components/CreateAccessTokenDialog.tsx +++ b/web/src/components/CreateAccessTokenDialog.tsx @@ -13,21 +13,6 @@ interface Props extends DialogProps { onConfirm: () => void; } -const expirationOptions = [ - { - label: "8 hours", - value: 3600 * 8, - }, - { - label: "1 month", - value: 3600 * 24 * 30, - }, - { - label: "Never", - value: 0, - }, -]; - interface State { description: string; expiration: number; @@ -43,6 +28,21 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => { }); const requestState = useLoading(false); + const expirationOptions = [ + { + label: t("setting.access-token-section.create-dialog.duration-8h"), + value: 3600 * 8, + }, + { + label: t("setting.access-token-section.create-dialog.duration-1m"), + value: 3600 * 24 * 30, + }, + { + label: t("setting.access-token-section.create-dialog.duration-never"), + value: 0, + }, + ]; + const setPartialState = (partialState: Partial) => { setState({ ...state, @@ -64,7 +64,7 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => { const handleSaveBtnClick = async () => { if (!state.description) { - toast.error("Description is required"); + toast.error(t("message.description-is-required")); return; } @@ -86,7 +86,7 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => { return ( <>
-

Create access token

+

{t("setting.access-token-section.create-dialog.create-access-token")}

@@ -94,13 +94,13 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => {
- Description * + {t("setting.access-token-section.create-dialog.description")} *
@@ -108,7 +108,7 @@ const CreateAccessTokenDialog: React.FC = (props: Props) => {
- Expiration * + {t("setting.access-token-section.create-dialog.expiration")} *
diff --git a/web/src/components/CreateWebhookDialog.tsx b/web/src/components/CreateWebhookDialog.tsx index 441e16075..bf9f870d2 100644 --- a/web/src/components/CreateWebhookDialog.tsx +++ b/web/src/components/CreateWebhookDialog.tsx @@ -63,7 +63,7 @@ const CreateWebhookDialog: React.FC = (props: Props) => { const handleSaveBtnClick = async () => { if (!state.name || !state.url) { - toast.error("Please fill all required fields"); + toast.error(t("message.fill-all-required-fields")); return; } @@ -95,7 +95,9 @@ const CreateWebhookDialog: React.FC = (props: Props) => { return ( <>
-

{isCreating ? "Create webhook" : "Edit webhook"}

+

+ {isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")} +

@@ -103,13 +105,13 @@ const CreateWebhookDialog: React.FC = (props: Props) => {
- Title * + {t("setting.webhook-section.create-dialog.title")} *
@@ -117,13 +119,13 @@ const CreateWebhookDialog: React.FC = (props: Props) => {
- Payload URL * + {t("setting.webhook-section.create-dialog.payload-url")} *
diff --git a/web/src/components/MemoDisplaySettingMenu.tsx b/web/src/components/MemoDisplaySettingMenu.tsx index 37ae4eb83..af4f94ea6 100644 --- a/web/src/components/MemoDisplaySettingMenu.tsx +++ b/web/src/components/MemoDisplaySettingMenu.tsx @@ -2,6 +2,7 @@ import { Option, Select } from "@mui/joy"; import clsx from "clsx"; import { Settings2Icon } from "lucide-react"; import { useMemoFilterStore } from "@/store/v1"; +import { useTranslate } from "@/utils/i18n"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover"; interface Props { @@ -9,6 +10,7 @@ interface Props { } const MemoDisplaySettingMenu = ({ className }: Props) => { + const t = useTranslate(); const memoFilterStore = useMemoFilterStore(); const isApplying = Boolean(memoFilterStore.orderByTimeAsc) !== false; @@ -22,16 +24,16 @@ const MemoDisplaySettingMenu = ({ className }: Props) => {
- Order by + {t("memo.order-by")}
- Direction + {t("memo.direction")}
diff --git a/web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx b/web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx index 62fd1876c..091c53e08 100644 --- a/web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx +++ b/web/src/components/MemoEditor/ActionButton/AddMemoRelationPopover.tsx @@ -92,7 +92,7 @@ const AddMemoRelationPopover = (props: Props) => { // If embedded mode is enabled, embed the memo instead of creating a relation. if (embedded) { if (!editorRef.current) { - toast.error("Failed to embed memo"); + toast.error(t("message.failed-to-embed-memo")); return; } diff --git a/web/src/components/MemoFilters.tsx b/web/src/components/MemoFilters.tsx index 4527599f6..0bd2fe8bb 100644 --- a/web/src/components/MemoFilters.tsx +++ b/web/src/components/MemoFilters.tsx @@ -3,8 +3,10 @@ import { CalendarIcon, CheckCircleIcon, CodeIcon, EyeIcon, FilterIcon, LinkIcon, import { useEffect, useRef } from "react"; import { useSearchParams } from "react-router-dom"; import { FilterFactor, getMemoFilterKey, MemoFilter, parseFilterQuery, stringifyFilters, useMemoFilterStore } from "@/store/v1"; +import { useTranslate } from "@/utils/i18n"; const MemoFilters = () => { + const t = useTranslate(); const [searchParams, setSearchParams] = useSearchParams(); const memoFilterStore = useMemoFilterStore(); const filters = memoFilterStore.filters; @@ -75,7 +77,7 @@ const MemoFilters = () => {
- Filters + {t("memo.filters")}
{filters.map((filter) => ( diff --git a/web/src/components/Settings/AccessTokenSection.tsx b/web/src/components/Settings/AccessTokenSection.tsx index ff849896c..52496fcff 100644 --- a/web/src/components/Settings/AccessTokenSection.tsx +++ b/web/src/components/Settings/AccessTokenSection.tsx @@ -33,13 +33,12 @@ const AccessTokenSection = () => { const copyAccessToken = (accessToken: string) => { copy(accessToken); - toast.success("Access token copied to clipboard"); + toast.success(t("setting.access-token-section.access-token-copied-to-clipboard")); }; const handleDeleteAccessToken = async (accessToken: string) => { - const confirmed = window.confirm( - `Are you sure to delete access token \`${getFormatedAccessToken(accessToken)}\`? You cannot undo this action.`, - ); + const formatedAccessToken = getFormatedAccessToken(accessToken); + const confirmed = window.confirm(t("setting.access-token-section.access-token-deletion", { accessToken: formatedAccessToken })); if (confirmed) { await userServiceClient.deleteUserAccessToken({ name: currentUser.name, accessToken: accessToken }); setUserAccessTokens(userAccessTokens.filter((token) => token.accessToken !== accessToken)); @@ -85,10 +84,10 @@ const AccessTokenSection = () => { {t("common.description")} - {t("setting.access-token-section.created-at")} + {t("setting.access-token-section.create-dialog.created-at")} - {t("setting.access-token-section.expires-at")} + {t("setting.access-token-section.create-dialog.expires-at")} {t("common.delete")} @@ -111,7 +110,7 @@ const AccessTokenSection = () => { {userAccessToken.issuedAt?.toLocaleString()} - {userAccessToken.expiresAt?.toLocaleString() ?? "Never"} + {userAccessToken.expiresAt?.toLocaleString() ?? t("setting.access-token-section.create-dialog.duration-never")}
diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 33c4e08fd..3b54c7a44 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -24,6 +24,7 @@ "collapse": "Collapse", "create": "Create", "database": "Database", + "day": "Day", "days": "Days", "delete": "Delete", "description": "Description", @@ -34,12 +35,14 @@ "file": "File", "filter": "Filter", "home": "Home", + "in": "In", "image": "Image", "inbox": "Inbox", "language": "Language", "learn-more": "Learn more", "link": "Link", "mark": "Mark", + "memo": "Memo", "memos": "Memos", "name": "Name", "new": "New", @@ -103,7 +106,7 @@ "write-a-comment": "Write a comment" }, "copy-link": "Copy Link", - "count-memos-in-date": "{{count}} memos in {{date}}", + "count-memos-in-date": "{{count}} {{memos}} in {{date}}", "delete-confirm": "Are you sure you want to delete this memo? THIS ACTION IS IRREVERSIBLE", "load-more": "Load more", "no-archived-memos": "No archived memos.", @@ -121,7 +124,14 @@ "to-do": "To-do", "code": "Code", "remove-completed-task-list-items": "Remove done", - "remove-completed-task-list-items-confirm": "Are you sure you want to remove all completed to-dos? THIS ACTION IS IRREVERSIBLE" + "remove-completed-task-list-items-confirm": "Are you sure you want to remove all completed to-dos? THIS ACTION IS IRREVERSIBLE", + "filters": "Filters", + "order-by": "Order By", + "display-time": "Display Time", + "direction": "Direction", + "direction-desc": "Descending", + "direction-asc": "Ascending", + "no-memos": "No memos." }, "message": { "archived-successfully": "Archived successfully", @@ -139,7 +149,10 @@ "succeed-copy-link": "Link copied successfully.", "update-succeed": "Update succeeded", "user-not-found": "User not found", - "remove-completed-task-list-items-successfully": "The removal was successful" + "remove-completed-task-list-items-successfully": "The removal was successful", + "failed-to-embed-memo": "Failed to embed memo", + "description-is-required": "Description is required", + "fill-all-required-fields": "Please fill all required fields" }, "reference": { "add-references": "Add references", @@ -307,17 +320,36 @@ "removed-completed-task-list-items": "Enable removal of completed task list items" }, "memo-related": "Memo", - "access-token-section":{ + "access-token-section": { "title": "Access Tokens", "description": "A list of all access tokens for your account.", - "created-at": "Created At", - "expires-at": "Expires At", - "token": "Token" + "token": "Token", + "access-token-deletion": "Are you sure to delete access token {{accessToken}}? THIS ACTION IS IRREVERSIBLE.", + "access-token-copied-to-clipboard": "Access token copied to clipboard", + "create-dialog": { + "create-access-token": "Create Access Token", + "description": "Description", + "some-description": "Some description...", + "expiration": "Expiration", + "created-at": "Created At", + "expires-at": "Expires At", + "duration-never": "Never", + "duration-8h": "8 Hours", + "duration-1m": "1 Month" + } }, "webhook-section": { "title": "Webhooks", - "url": "Url", - "no-webhooks-found": "No webhooks found." + "url": "URL", + "no-webhooks-found": "No webhooks found.", + "create-dialog": { + "create-webhook": "Create webhook", + "edit-webhook": "Edit webhook", + "an-easy-to-remember-name": "An easy-to-remember name", + "title": "Title", + "payload-url": "Payload URL", + "url-example-post-receive": "https://example.com/postreceive" + } }, "workspace-section": { "disallow-user-registration": "Disallow user registration", @@ -334,7 +366,7 @@ "enable-link-preview": "Enable link preview", "enable-memo-comments": "Enable memo comments", "enable-memo-location": "Enable memo location", - "content-lenght-limit": "Content length limit(Byte)", + "content-lenght-limit": "Content length limit (Byte)", "reactions": "Reactions" }, "version": "Version" @@ -351,5 +383,12 @@ "code-block": "Code block", "checkbox": "Checkbox", "content-syntax": "Content syntax" + }, + "about": { + "description": "A privacy-first, lightweight note-taking service. Easily capture and share your great thoughts.", + "github-repository": "GitHub Repo", + "official-website": "Official Website", + "blogs": "Blogs", + "documents": "Documents" } } diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index dc828c584..d639ca798 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -24,6 +24,7 @@ "collapse": "Réduire", "create": "Créer", "database": "Base de données", + "day": "Jour", "days": "Jours", "delete": "Supprimer", "description": "Description", @@ -34,12 +35,14 @@ "file": "Fichier", "filter": "Filtre", "home": "Accueil", + "in": "En", "image": "Image", "inbox": "Notifications", "language": "Langue", "learn-more": "En savoir plus", "link": "Lien", "mark": "Lier", + "memo": "Memo", "memos": "Memos", "name": "Nom", "new": "Nouveau", @@ -103,7 +106,7 @@ "write-a-comment": "Écrire un commentaire" }, "copy-link": "Copier le lien", - "count-memos-in-date": "{{count}} memos le {{date}}", + "count-memos-in-date": "{{count}} {{memos}} le {{date}}", "delete-confirm": "Êtes-vous sûr de vouloir supprimer ce memos ? CETTE ACTION EST IRRÉVERSIBLE", "load-more": "Charger plus", "no-archived-memos": "Pas de memos archivés.", @@ -121,7 +124,14 @@ "to-do": "À faire", "code": "Code", "remove-completed-task-list-items": "Supprimer terminées", - "remove-completed-task-list-items-confirm": "Êtes-vous sûr de vouloir supprimer toutes les tâches terminées ? (Cette action est irréversible)" + "remove-completed-task-list-items-confirm": "Êtes-vous sûr de vouloir supprimer toutes les tâches terminées ? CETTE ACTION EST IRRÉVERSIBLE", + "filters": "Filtres", + "order-by": "Ordonner par", + "display-time": "Date d'Affichage", + "direction": "Ordre", + "direction-desc": "Descendant", + "direction-asc": "Ascendant", + "no-memos": "Pas de memos." }, "message": { "archived-successfully": "Archivé avec succès", @@ -139,7 +149,10 @@ "succeed-copy-link": "Succeed to copy link to clipboard.", "update-succeed": "Mise à jour effectuée", "user-not-found": "Utilisateur introuvable", - "remove-completed-task-list-items-successfully": "Supprimé avec succès !" + "remove-completed-task-list-items-successfully": "Supprimé avec succès !", + "failed-to-embed-memo": "Échec de l'intégration du memo.", + "message.description-is-required": "Une description est requise", + "fill-all-required-fields": "Merci de remplir tous les champs requis" }, "reference": { "add-references": "Ajouter des références", @@ -155,7 +168,7 @@ "file-name": "Nom du fichier", "file-name-placeholder": "Nom du fichier", "link": "Lien", - "link-placeholder": "https://the.link.to/your/resource", + "link-placeholder": "https://le.lien.vers/votre/ressource", "option": "Lien externe", "type": "Type", "type-placeholder": "Type de fichier" @@ -307,17 +320,36 @@ "removed-completed-task-list-items": "Activer la suppression terminée" }, "memo-related": "Memo", - "access-token-section":{ - "title": "Jetons d'Accès", + "access-token-section": { + "title": "Jetons d'accès", "description": "Une liste de tous les jetons d'accès pour votre compte.", - "created-at": "Créé le", - "expires-at": "Expire le", - "token": "Jeton" + "token": "Jeton", + "access-token-deletion": "Êtes-vous sûr de vouloir supprimer le jeton d'accès {{accessToken}}? CETTE ACTION EST IRRÉVERSIBLE.", + "access-token-copied-to-clipboard": "Jeton d'accès copié dans le presse-papier", + "create-dialog": { + "create-access-token": "Créer un jeton d'accès", + "description-label": "Description", + "some-description": "Une description…", + "expiration-label": "Expiration", + "created-at": "Créé le", + "expires-at": "Expire le", + "duration-never": "Jamais", + "duration-8h": "8 Heures", + "duration-1m": "1 Mois" + } }, "webhook-section": { "title": "Webhooks", - "url": "Url", - "no-webhooks-found": "Aucun webhook trouvé." + "url": "URL", + "no-webhooks-found": "Aucun webhook trouvé.", + "create-dialog": { + "create-webhook": "Créer un webhook", + "edit-webhook": "Éditer le webhook", + "an-easy-to-remember-name": "Un nom facile à retenir", + "title": "Titre", + "payload-url": "URL de payload", + "url-example-post-receive": "https://exemple.com/postreceive" + } }, "workspace-section": { "disallow-user-registration": "Interdire l'inscription de nouveaux utilisateurs", @@ -334,7 +366,7 @@ "enable-link-preview": "Activer la prévisualisation de liens", "enable-memo-comments": "Activer les commentaires des memos", "enable-memo-location": "Activer la géolocalisation des memos", - "content-lenght-limit": "Taille maximum de contenu(Octet)", + "content-lenght-limit": "Taille maximum de contenu (Octet)", "reactions": "Réactions" }, "version": "Version" @@ -351,5 +383,12 @@ "code-block": "Bloc de code", "checkbox": "Case à cocher", "content-syntax": "Syntaxe du contenu" + }, + "about": { + "description": "Un service de prise de notes léger et respectueux de la vie privée. Capturez et partagez facilement vos meilleures idées.", + "github-repository": "Dépôt GitHub", + "official-website": "Site web officiel", + "blogs": "Blogs", + "documents": "Documents" } } diff --git a/web/src/pages/About.tsx b/web/src/pages/About.tsx index 4361b5408..f55d0d289 100644 --- a/web/src/pages/About.tsx +++ b/web/src/pages/About.tsx @@ -1,8 +1,11 @@ import { Link } from "@mui/joy"; import { DotIcon } from "lucide-react"; import MobileHeader from "@/components/MobileHeader"; +import { useTranslate } from "@/utils/i18n"; const About = () => { + const t = useTranslate(); + return (
@@ -11,22 +14,22 @@ const About = () => { memos -

A privacy-first, lightweight note-taking service. Easily capture and share your great thoughts.

+

{t("about.description")}

- GitHub Repo + {t("about.github-repository")} - Official Website + {t("about.official-website")} - Blogs + {t("about.blogs")} - Documents + {t("about.documents")}