chore: adding missing translations (#4273)

* Added missing translations strings (filters, about page, ...)

* Forgot one translation string.

* Fixed PR issues and added Access Token related missing translation strings.

* Fixed eslint issues.

* Fixed eslint issues #2.

* Fixed access token dialog translations, added missing webhook dialog translations.
pull/4286/head
Pierre Quillery 6 months ago committed by GitHub
parent 7b909fb772
commit cdadf133d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 (

@ -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: 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<State>) => {
setState({
...state,
@ -64,7 +64,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (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: Props) => {
return (
<>
<div className="dialog-header-container">
<p className="title-text">Create access token</p>
<p className="title-text">{t("setting.access-token-section.create-dialog.create-access-token")}</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
@ -94,13 +94,13 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
<div className="dialog-content-container !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
Description <span className="text-red-600">*</span>
{t("setting.access-token-section.create-dialog.description")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder="Some description"
placeholder={t("setting.access-token-section.create-dialog.some-description")}
value={state.description}
onChange={handleDescriptionInputChange}
/>
@ -108,7 +108,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
Expiration <span className="text-red-600">*</span>
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
</span>
<div className="w-full flex flex-row justify-start items-center text-base">
<RadioGroup orientation="horizontal" value={state.expiration} onChange={handleRoleInputChange}>

@ -63,7 +63,7 @@ const CreateWebhookDialog: React.FC<Props> = (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: Props) => {
return (
<>
<div className="dialog-header-container">
<p className="title-text">{isCreating ? "Create webhook" : "Edit webhook"}</p>
<p className="title-text">
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
</p>
<Button size="sm" variant="plain" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
@ -103,13 +105,13 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
<div className="dialog-content-container !w-80">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
Title <span className="text-red-600">*</span>
{t("setting.webhook-section.create-dialog.title")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder="An easy-to-remember name"
placeholder={t("setting.webhook-section.create-dialog.an-easy-to-remember-name")}
value={state.name}
onChange={handleTitleInputChange}
/>
@ -117,13 +119,13 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
Payload URL <span className="text-red-600">*</span>
{t("setting.webhook-section.create-dialog.payload-url")} <span className="text-red-600">*</span>
</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder="https://example.com/postreceive"
placeholder={t("setting.webhook-section.create-dialog.url-example-post-receive")}
value={state.url}
onChange={handleUrlInputChange}
/>

@ -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) => {
<PopoverContent align="end" alignOffset={-12} sideOffset={14}>
<div className="flex flex-col gap-2">
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3">Order by</span>
<span className="text-sm shrink-0 mr-3">{t("memo.order-by")}</span>
<Select value="displayTime">
<Option value={"displayTime"}>Display Time</Option>
<Option value={"displayTime"}>{t("memo.display-time")}</Option>
</Select>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3">Direction</span>
<span className="text-sm shrink-0 mr-3">{t("memo.direction")}</span>
<Select value={memoFilterStore.orderByTimeAsc} onChange={(_, value) => memoFilterStore.setOrderByTimeAsc(Boolean(value))}>
<Option value={false}>DESC</Option>
<Option value={true}>ASC</Option>
<Option value={false}>{t("memo.direction-desc")}</Option>
<Option value={true}>{t("memo.direction-asc")}</Option>
</Select>
</div>
</div>

@ -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;
}

@ -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 = () => {
<div className="w-full mb-2 flex flex-row justify-start items-start gap-2">
<span className="flex flex-row items-center gap-0.5 text-gray-500 text-sm leading-6 border border-transparent">
<FilterIcon className="w-4 h-auto opacity-60 inline" />
Filters
{t("memo.filters")}
</span>
<div className="flex flex-row justify-start items-center flex-wrap gap-2 leading-6 h-6">
{filters.map((filter) => (

@ -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")}
</th>
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
{t("setting.access-token-section.created-at")}
{t("setting.access-token-section.create-dialog.created-at")}
</th>
<th scope="col" className="px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:text-gray-400">
{t("setting.access-token-section.expires-at")}
{t("setting.access-token-section.create-dialog.expires-at")}
</th>
<th scope="col" className="relative py-3.5 pl-3 pr-4">
<span className="sr-only">{t("common.delete")}</span>
@ -111,7 +110,7 @@ const AccessTokenSection = () => {
{userAccessToken.issuedAt?.toLocaleString()}
</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
{userAccessToken.expiresAt?.toLocaleString() ?? "Never"}
{userAccessToken.expiresAt?.toLocaleString() ?? t("setting.access-token-section.create-dialog.duration-never")}
</td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button

@ -31,6 +31,9 @@ const UserStatisticsView = () => {
const [visibleMonthString, setVisibleMonthString] = useState(dayjs(selectedDate.toDateString()).format("YYYY-MM"));
const days = Math.ceil((Date.now() - currentUser.createTime!.getTime()) / 86400000);
const singularOrPluralMemo = (memoAmount > 0 ? t("common.memos") : t("common.memo")).toLowerCase();
const singularOrPluralDay = (days > 0 ? t("common.days") : t("common.day")).toLowerCase();
useAsyncEffect(async () => {
const memoStats: UserMemoStats = { link: 0, taskList: 0, code: 0, incompleteTasks: 0 };
metadataList.forEach((memo) => {
@ -90,14 +93,10 @@ const UserStatisticsView = () => {
onClick={onCalendarClick}
/>
{memoAmount === 0 ? (
<p className="mt-1 w-full text-xs italic opacity-80">No memos</p>
) : memoAmount === 1 ? (
<p className="mt-1 w-full text-xs italic opacity-80">
<span>{memoAmount}</span> memo in <span>{days}</span> {days > 1 ? "days" : "day"}
</p>
<p className="mt-1 w-full text-xs italic opacity-80">{t("memo.no-memos")}</p>
) : (
<p className="mt-1 w-full text-xs italic opacity-80">
<span>{memoAmount}</span> memos in <span>{days}</span> {days > 1 ? "days" : "day"}
<span>{memoAmount}</span> {singularOrPluralMemo} {t("common.in").toLowerCase()} <span>{days}</span> {singularOrPluralDay}
</p>
)}
</div>

@ -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"
}
}

@ -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"
}
}

@ -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 (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader />
@ -11,22 +14,22 @@ const About = () => {
<a href="https://www.usememos.com" target="_blank">
<img className="w-auto h-12" src="https://www.usememos.com/full-logo-landscape.png" alt="memos" />
</a>
<p className="text-base">A privacy-first, lightweight note-taking service. Easily capture and share your great thoughts.</p>
<p className="text-base">{t("about.description")}</p>
<div className="mt-1 flex flex-row items-center flex-wrap">
<Link underline="always" href="https://www.github.com/usememos/memos" target="_blank">
GitHub Repo
{t("about.github-repository")}
</Link>
<DotIcon className="w-4 h-auto opacity-60" />
<Link underline="always" href="https://www.usememos.com/" target="_blank">
Official Website
{t("about.official-website")}
</Link>
<DotIcon className="w-4 h-auto opacity-60" />
<Link underline="always" href="https://www.usememos.com/blog" target="_blank">
Blogs
{t("about.blogs")}
</Link>
<DotIcon className="w-4 h-auto opacity-60" />
<Link underline="always" href="https://www.usememos.com/docs" target="_blank">
Documents
{t("about.documents")}
</Link>
</div>
</div>

Loading…
Cancel
Save