From fa4f3d6e80bc14ff085b9098f120c3e1f6f1f675 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Sat, 9 May 2026 03:12:52 -0300 Subject: [PATCH] feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three modal dialogs that fire during the editor's routine save + share + auth-required flows are now fully localised across all 9 locales. LoginPromptModal - Title, body, and the three buttons (Sign in / Create account / Cancel). Sign in / Sign up Links use localize() so a Spanish reader prompted to log in lands at /es/login rather than dropping back to English. SaveProjectModal - Title (toggles between Save / Update), name + description fields with placeholders, save button (toggles between Save / Update / Saving…), Cancel button. - Visibility toggle: Public / Private label + hint copy under the icon. - All four error paths now go through t() with a {{status}} interpolation for the generic HTTP failure message. ShareModal - Title, public/private label + hint pair, "Make private" / "Make public" toggle, Copy button, the warning shown when the project is private, and the Close button. Hand-curated translations for all 8 non-English locales. Status codes (403) and shortcut markers preserved. --- .../components/layout/LoginPromptModal.tsx | 40 ++++++++++-------- .../components/layout/SaveProjectModal.tsx | 41 ++++++++++++------ frontend/src/components/layout/ShareModal.tsx | 24 ++++++----- frontend/src/i18n/locales/de/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/en/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/es/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/fr/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/it/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/ja/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/pt-br/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/ru/common.json | 42 +++++++++++++++++++ frontend/src/i18n/locales/zh-cn/common.json | 42 +++++++++++++++++++ 12 files changed, 442 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/layout/LoginPromptModal.tsx b/frontend/src/components/layout/LoginPromptModal.tsx index 679b2d82..a564dcbf 100644 --- a/frontend/src/components/layout/LoginPromptModal.tsx +++ b/frontend/src/components/layout/LoginPromptModal.tsx @@ -1,28 +1,34 @@ import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useLocalizedHref } from '../../i18n/useLocalizedNavigate'; interface LoginPromptModalProps { onClose: () => void; } -export const LoginPromptModal: React.FC = ({ onClose }) => ( -
-
e.stopPropagation()}> -

Sign in to save your project

-

Create a free account to save and share your projects.

-
- - Sign in - - - Create account - - +export const LoginPromptModal: React.FC = ({ onClose }) => { + const { t } = useTranslation(); + const localize = useLocalizedHref(); + return ( +
+
e.stopPropagation()}> +

{t('editor.loginPrompt.title')}

+

{t('editor.loginPrompt.body')}

+
+ + {t('header.auth.signIn')} + + + {t('editor.loginPrompt.createAccount')} + + +
-
-); + ); +}; const styles: Record = { overlay: { diff --git a/frontend/src/components/layout/SaveProjectModal.tsx b/frontend/src/components/layout/SaveProjectModal.tsx index c2eb6579..a7ba91b3 100644 --- a/frontend/src/components/layout/SaveProjectModal.tsx +++ b/frontend/src/components/layout/SaveProjectModal.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { useProjectStore } from '../../store/useProjectStore'; import { createProject, updateProject } from '../../services/projectService'; import { trackCreateProject, trackSaveProject } from '../../utils/analytics'; @@ -10,6 +11,7 @@ interface SaveProjectModalProps { } export const SaveProjectModal: React.FC = ({ onClose }) => { + const { t } = useTranslation(); const navigate = useNavigate(); const currentProject = useProjectStore((s) => s.currentProject); const setCurrentProject = useProjectStore((s) => s.setCurrentProject); @@ -32,7 +34,7 @@ export const SaveProjectModal: React.FC = ({ onClose }) = const handleSave = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { - setError('Project name is required.'); + setError(t('editor.saveProject.errors.nameRequired')); return; } setSaving(true); @@ -67,11 +69,14 @@ export const SaveProjectModal: React.FC = ({ onClose }) = onClose(); } catch (err: any) { if (!err?.response) { - setError('Server unreachable. Check your connection and try again.'); + setError(t('editor.saveProject.errors.unreachable')); } else if (err.response.status === 401) { - setError('Not authenticated. Please log in and try again.'); + setError(t('editor.saveProject.errors.notAuth')); } else { - setError(err.response?.data?.detail || `Save failed (${err.response.status}).`); + setError( + err.response?.data?.detail || + t('editor.saveProject.errors.failedStatus', { status: err.response.status }) + ); } } finally { setSaving(false); @@ -81,12 +86,14 @@ export const SaveProjectModal: React.FC = ({ onClose }) = return (
e.stopPropagation()}> -

{isUpdate ? 'Update project' : 'Save project'}

+

+ {isUpdate ? t('editor.saveProject.titleUpdate') : t('editor.saveProject.titleSave')} +

{error &&
{error}
}
- + = ({ onClose }) = required style={styles.input} autoFocus - placeholder="My awesome project" + placeholder={t('editor.saveProject.namePlaceholder')} /> - + setDescription(e.target.value)} style={styles.input} - placeholder="Optional" + placeholder={t('editor.saveProject.descriptionPlaceholder')} />
= ({ onClose }) =
- {isPublic ? 'Public' : 'Private'} + {isPublic + ? t('editor.saveProject.visibility.public') + : t('editor.saveProject.visibility.private')}
- {isPublic ? 'Anyone with the link can view' : 'Only you can see this'} + {isPublic + ? t('editor.saveProject.visibility.publicHint') + : t('editor.saveProject.visibility.privateHint')}
@@ -158,10 +169,14 @@ export const SaveProjectModal: React.FC = ({ onClose }) =
diff --git a/frontend/src/components/layout/ShareModal.tsx b/frontend/src/components/layout/ShareModal.tsx index 3325952f..904170a3 100644 --- a/frontend/src/components/layout/ShareModal.tsx +++ b/frontend/src/components/layout/ShareModal.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; import { useProjectStore } from '../../store/useProjectStore'; import { updateProject } from '../../services/projectService'; @@ -12,6 +13,7 @@ interface ShareModalProps { } export const ShareModal: React.FC = ({ onClose }) => { + const { t } = useTranslation(); const currentProject = useProjectStore((s) => s.currentProject); const setVisibility = useProjectStore((s) => s.setVisibility); const [copied, setCopied] = useState(false); @@ -44,7 +46,7 @@ export const ShareModal: React.FC = ({ onClose }) => { return createPortal(
e.stopPropagation()}> -

Share project

+

{t('editor.share.title')}

{/* Visibility toggle */}
@@ -83,13 +85,11 @@ export const ShareModal: React.FC = ({ onClose }) => { - {isPublic ? 'Public' : 'Private'} + {isPublic ? t('editor.share.public') : t('editor.share.private')} - {isPublic - ? 'Anyone with the link can view this project' - : 'Only you can see this project'} + {isPublic ? t('editor.share.publicHint') : t('editor.share.privateHint')}
@@ -128,20 +132,18 @@ export const ShareModal: React.FC = ({ onClose }) => { ) : ( - 'Copy' + t('editor.share.copy') )}
{!isPublic && ( -
- This project is private. Others will see a 403 error when opening this link. -
+
{t('editor.share.privateWarning')}
)}
diff --git a/frontend/src/i18n/locales/de/common.json b/frontend/src/i18n/locales/de/common.json index 5c876c21..5b70ecb7 100644 --- a/frontend/src/i18n/locales/de/common.json +++ b/frontend/src/i18n/locales/de/common.json @@ -137,6 +137,48 @@ "confirmClose": "Datei mit nicht gespeicherten Änderungen schließen?", "closeAnyway": "Trotzdem schließen", "cancel": "Abbrechen" + }, + "loginPrompt": { + "title": "Anmelden, um dein Projekt zu speichern", + "body": "Erstelle ein kostenloses Konto, um deine Projekte zu speichern und zu teilen.", + "createAccount": "Konto erstellen", + "cancel": "Abbrechen" + }, + "saveProject": { + "titleSave": "Projekt speichern", + "titleUpdate": "Projekt aktualisieren", + "nameLabel": "Projektname *", + "namePlaceholder": "Mein tolles Projekt", + "descriptionLabel": "Beschreibung", + "descriptionPlaceholder": "Optional", + "save": "Speichern", + "update": "Aktualisieren", + "saving": "Wird gespeichert…", + "cancel": "Abbrechen", + "visibility": { + "public": "Öffentlich", + "publicHint": "Jeder mit dem Link kann es sehen", + "private": "Privat", + "privateHint": "Nur du kannst es sehen" + }, + "errors": { + "nameRequired": "Projektname ist erforderlich.", + "unreachable": "Server nicht erreichbar. Verbindung prüfen und erneut versuchen.", + "notAuth": "Nicht angemeldet. Bitte anmelden und erneut versuchen.", + "failedStatus": "Speichern fehlgeschlagen ({{status}})." + } + }, + "share": { + "title": "Projekt teilen", + "public": "Öffentlich", + "private": "Privat", + "publicHint": "Jeder mit dem Link kann dieses Projekt sehen", + "privateHint": "Nur du kannst dieses Projekt sehen", + "makePrivate": "Auf privat setzen", + "makePublic": "Auf öffentlich setzen", + "copy": "Kopieren", + "privateWarning": "Dieses Projekt ist privat. Andere sehen beim Öffnen des Links einen 403-Fehler.", + "close": "Schließen" } } } diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 6876a49f..a0d451af 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -137,6 +137,48 @@ "confirmClose": "Close file with unsaved changes?", "closeAnyway": "Close anyway", "cancel": "Cancel" + }, + "loginPrompt": { + "title": "Sign in to save your project", + "body": "Create a free account to save and share your projects.", + "createAccount": "Create account", + "cancel": "Cancel" + }, + "saveProject": { + "titleSave": "Save project", + "titleUpdate": "Update project", + "nameLabel": "Project name *", + "namePlaceholder": "My awesome project", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Optional", + "save": "Save", + "update": "Update", + "saving": "Saving…", + "cancel": "Cancel", + "visibility": { + "public": "Public", + "publicHint": "Anyone with the link can view", + "private": "Private", + "privateHint": "Only you can see this" + }, + "errors": { + "nameRequired": "Project name is required.", + "unreachable": "Server unreachable. Check your connection and try again.", + "notAuth": "Not authenticated. Please log in and try again.", + "failedStatus": "Save failed ({{status}})." + } + }, + "share": { + "title": "Share project", + "public": "Public", + "private": "Private", + "publicHint": "Anyone with the link can view this project", + "privateHint": "Only you can see this project", + "makePrivate": "Make private", + "makePublic": "Make public", + "copy": "Copy", + "privateWarning": "This project is private. Others will see a 403 error when opening this link.", + "close": "Close" } } } diff --git a/frontend/src/i18n/locales/es/common.json b/frontend/src/i18n/locales/es/common.json index cd780e83..39873647 100644 --- a/frontend/src/i18n/locales/es/common.json +++ b/frontend/src/i18n/locales/es/common.json @@ -137,6 +137,48 @@ "confirmClose": "¿Cerrar archivo con cambios sin guardar?", "closeAnyway": "Cerrar igualmente", "cancel": "Cancelar" + }, + "loginPrompt": { + "title": "Inicia sesión para guardar tu proyecto", + "body": "Crea una cuenta gratis para guardar y compartir tus proyectos.", + "createAccount": "Crear cuenta", + "cancel": "Cancelar" + }, + "saveProject": { + "titleSave": "Guardar proyecto", + "titleUpdate": "Actualizar proyecto", + "nameLabel": "Nombre del proyecto *", + "namePlaceholder": "Mi proyecto increíble", + "descriptionLabel": "Descripción", + "descriptionPlaceholder": "Opcional", + "save": "Guardar", + "update": "Actualizar", + "saving": "Guardando…", + "cancel": "Cancelar", + "visibility": { + "public": "Público", + "publicHint": "Cualquiera con el enlace puede verlo", + "private": "Privado", + "privateHint": "Solo tú puedes verlo" + }, + "errors": { + "nameRequired": "El nombre del proyecto es obligatorio.", + "unreachable": "Servidor no disponible. Comprueba la conexión e inténtalo de nuevo.", + "notAuth": "No autenticado. Inicia sesión e inténtalo de nuevo.", + "failedStatus": "Error al guardar ({{status}})." + } + }, + "share": { + "title": "Compartir proyecto", + "public": "Público", + "private": "Privado", + "publicHint": "Cualquiera con el enlace puede ver este proyecto", + "privateHint": "Solo tú puedes ver este proyecto", + "makePrivate": "Hacer privado", + "makePublic": "Hacer público", + "copy": "Copiar", + "privateWarning": "Este proyecto es privado. Otros verán un error 403 al abrir este enlace.", + "close": "Cerrar" } } } diff --git a/frontend/src/i18n/locales/fr/common.json b/frontend/src/i18n/locales/fr/common.json index 057ca019..1691feee 100644 --- a/frontend/src/i18n/locales/fr/common.json +++ b/frontend/src/i18n/locales/fr/common.json @@ -137,6 +137,48 @@ "confirmClose": "Fermer le fichier avec des modifications non enregistrées ?", "closeAnyway": "Fermer quand même", "cancel": "Annuler" + }, + "loginPrompt": { + "title": "Connectez-vous pour enregistrer votre projet", + "body": "Créez un compte gratuit pour enregistrer et partager vos projets.", + "createAccount": "Créer un compte", + "cancel": "Annuler" + }, + "saveProject": { + "titleSave": "Enregistrer le projet", + "titleUpdate": "Mettre à jour le projet", + "nameLabel": "Nom du projet *", + "namePlaceholder": "Mon super projet", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Optionnel", + "save": "Enregistrer", + "update": "Mettre à jour", + "saving": "Enregistrement…", + "cancel": "Annuler", + "visibility": { + "public": "Public", + "publicHint": "Toute personne avec le lien peut voir", + "private": "Privé", + "privateHint": "Vous seul pouvez le voir" + }, + "errors": { + "nameRequired": "Le nom du projet est obligatoire.", + "unreachable": "Serveur injoignable. Vérifiez votre connexion et réessayez.", + "notAuth": "Non authentifié. Connectez-vous et réessayez.", + "failedStatus": "Échec de l'enregistrement ({{status}})." + } + }, + "share": { + "title": "Partager le projet", + "public": "Public", + "private": "Privé", + "publicHint": "Toute personne avec le lien peut voir ce projet", + "privateHint": "Vous seul pouvez voir ce projet", + "makePrivate": "Rendre privé", + "makePublic": "Rendre public", + "copy": "Copier", + "privateWarning": "Ce projet est privé. Les autres verront une erreur 403 en ouvrant ce lien.", + "close": "Fermer" } } } diff --git a/frontend/src/i18n/locales/it/common.json b/frontend/src/i18n/locales/it/common.json index c3a35011..699a8ae7 100644 --- a/frontend/src/i18n/locales/it/common.json +++ b/frontend/src/i18n/locales/it/common.json @@ -137,6 +137,48 @@ "confirmClose": "Chiudere il file con modifiche non salvate?", "closeAnyway": "Chiudi comunque", "cancel": "Annulla" + }, + "loginPrompt": { + "title": "Accedi per salvare il tuo progetto", + "body": "Crea un account gratuito per salvare e condividere i tuoi progetti.", + "createAccount": "Crea account", + "cancel": "Annulla" + }, + "saveProject": { + "titleSave": "Salva progetto", + "titleUpdate": "Aggiorna progetto", + "nameLabel": "Nome del progetto *", + "namePlaceholder": "Il mio fantastico progetto", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Opzionale", + "save": "Salva", + "update": "Aggiorna", + "saving": "Salvataggio…", + "cancel": "Annulla", + "visibility": { + "public": "Pubblico", + "publicHint": "Chiunque abbia il link può vederlo", + "private": "Privato", + "privateHint": "Solo tu puoi vederlo" + }, + "errors": { + "nameRequired": "Il nome del progetto è obbligatorio.", + "unreachable": "Server irraggiungibile. Controlla la connessione e riprova.", + "notAuth": "Non autenticato. Effettua l'accesso e riprova.", + "failedStatus": "Salvataggio fallito ({{status}})." + } + }, + "share": { + "title": "Condividi progetto", + "public": "Pubblico", + "private": "Privato", + "publicHint": "Chiunque abbia il link può vedere questo progetto", + "privateHint": "Solo tu puoi vedere questo progetto", + "makePrivate": "Rendi privato", + "makePublic": "Rendi pubblico", + "copy": "Copia", + "privateWarning": "Questo progetto è privato. Gli altri vedranno un errore 403 aprendo il link.", + "close": "Chiudi" } } } diff --git a/frontend/src/i18n/locales/ja/common.json b/frontend/src/i18n/locales/ja/common.json index 231de11f..d8cb4b83 100644 --- a/frontend/src/i18n/locales/ja/common.json +++ b/frontend/src/i18n/locales/ja/common.json @@ -137,6 +137,48 @@ "confirmClose": "未保存の変更があるファイルを閉じますか?", "closeAnyway": "それでも閉じる", "cancel": "キャンセル" + }, + "loginPrompt": { + "title": "プロジェクトを保存するにはサインインしてください", + "body": "無料アカウントを作成して、プロジェクトを保存・共有しましょう。", + "createAccount": "アカウントを作成", + "cancel": "キャンセル" + }, + "saveProject": { + "titleSave": "プロジェクトを保存", + "titleUpdate": "プロジェクトを更新", + "nameLabel": "プロジェクト名 *", + "namePlaceholder": "私の素敵なプロジェクト", + "descriptionLabel": "説明", + "descriptionPlaceholder": "任意", + "save": "保存", + "update": "更新", + "saving": "保存中…", + "cancel": "キャンセル", + "visibility": { + "public": "公開", + "publicHint": "リンクを持つ人なら誰でも閲覧できます", + "private": "非公開", + "privateHint": "あなただけが閲覧できます" + }, + "errors": { + "nameRequired": "プロジェクト名は必須です。", + "unreachable": "サーバーに接続できません。接続を確認してもう一度お試しください。", + "notAuth": "認証されていません。ログインしてもう一度お試しください。", + "failedStatus": "保存に失敗しました({{status}})。" + } + }, + "share": { + "title": "プロジェクトを共有", + "public": "公開", + "private": "非公開", + "publicHint": "リンクを持つ人なら誰でもこのプロジェクトを閲覧できます", + "privateHint": "このプロジェクトはあなただけが閲覧できます", + "makePrivate": "非公開にする", + "makePublic": "公開にする", + "copy": "コピー", + "privateWarning": "このプロジェクトは非公開です。他の人がこのリンクを開くと 403 エラーが表示されます。", + "close": "閉じる" } } } diff --git a/frontend/src/i18n/locales/pt-br/common.json b/frontend/src/i18n/locales/pt-br/common.json index ff3970da..d626536e 100644 --- a/frontend/src/i18n/locales/pt-br/common.json +++ b/frontend/src/i18n/locales/pt-br/common.json @@ -137,6 +137,48 @@ "confirmClose": "Fechar arquivo com alterações não salvas?", "closeAnyway": "Fechar mesmo assim", "cancel": "Cancelar" + }, + "loginPrompt": { + "title": "Entre para salvar seu projeto", + "body": "Crie uma conta gratuita para salvar e compartilhar seus projetos.", + "createAccount": "Criar conta", + "cancel": "Cancelar" + }, + "saveProject": { + "titleSave": "Salvar projeto", + "titleUpdate": "Atualizar projeto", + "nameLabel": "Nome do projeto *", + "namePlaceholder": "Meu projeto incrível", + "descriptionLabel": "Descrição", + "descriptionPlaceholder": "Opcional", + "save": "Salvar", + "update": "Atualizar", + "saving": "Salvando…", + "cancel": "Cancelar", + "visibility": { + "public": "Público", + "publicHint": "Qualquer pessoa com o link pode visualizar", + "private": "Privado", + "privateHint": "Somente você pode ver" + }, + "errors": { + "nameRequired": "O nome do projeto é obrigatório.", + "unreachable": "Servidor inacessível. Verifique sua conexão e tente novamente.", + "notAuth": "Não autenticado. Faça login e tente novamente.", + "failedStatus": "Falha ao salvar ({{status}})." + } + }, + "share": { + "title": "Compartilhar projeto", + "public": "Público", + "private": "Privado", + "publicHint": "Qualquer pessoa com o link pode ver este projeto", + "privateHint": "Somente você pode ver este projeto", + "makePrivate": "Tornar privado", + "makePublic": "Tornar público", + "copy": "Copiar", + "privateWarning": "Este projeto é privado. Outros verão um erro 403 ao abrir este link.", + "close": "Fechar" } } } diff --git a/frontend/src/i18n/locales/ru/common.json b/frontend/src/i18n/locales/ru/common.json index 046c05bc..6babd585 100644 --- a/frontend/src/i18n/locales/ru/common.json +++ b/frontend/src/i18n/locales/ru/common.json @@ -137,6 +137,48 @@ "confirmClose": "Закрыть файл с несохранёнными изменениями?", "closeAnyway": "Всё равно закрыть", "cancel": "Отмена" + }, + "loginPrompt": { + "title": "Войдите, чтобы сохранить проект", + "body": "Создайте бесплатный аккаунт, чтобы сохранять и делиться своими проектами.", + "createAccount": "Создать аккаунт", + "cancel": "Отмена" + }, + "saveProject": { + "titleSave": "Сохранить проект", + "titleUpdate": "Обновить проект", + "nameLabel": "Имя проекта *", + "namePlaceholder": "Мой потрясающий проект", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Необязательно", + "save": "Сохранить", + "update": "Обновить", + "saving": "Сохранение…", + "cancel": "Отмена", + "visibility": { + "public": "Публичный", + "publicHint": "Любой по ссылке может посмотреть", + "private": "Приватный", + "privateHint": "Видите только вы" + }, + "errors": { + "nameRequired": "Имя проекта обязательно.", + "unreachable": "Сервер недоступен. Проверьте соединение и попробуйте снова.", + "notAuth": "Не авторизованы. Войдите и попробуйте снова.", + "failedStatus": "Сохранение не удалось ({{status}})." + } + }, + "share": { + "title": "Поделиться проектом", + "public": "Публичный", + "private": "Приватный", + "publicHint": "Любой по ссылке может посмотреть этот проект", + "privateHint": "Этот проект видите только вы", + "makePrivate": "Сделать приватным", + "makePublic": "Сделать публичным", + "copy": "Копировать", + "privateWarning": "Этот проект приватный. Другие увидят ошибку 403 при открытии ссылки.", + "close": "Закрыть" } } } diff --git a/frontend/src/i18n/locales/zh-cn/common.json b/frontend/src/i18n/locales/zh-cn/common.json index 3365ee1d..4176f37a 100644 --- a/frontend/src/i18n/locales/zh-cn/common.json +++ b/frontend/src/i18n/locales/zh-cn/common.json @@ -137,6 +137,48 @@ "confirmClose": "确定关闭包含未保存更改的文件吗?", "closeAnyway": "仍然关闭", "cancel": "取消" + }, + "loginPrompt": { + "title": "登录以保存您的项目", + "body": "免费创建账户即可保存并分享您的项目。", + "createAccount": "创建账户", + "cancel": "取消" + }, + "saveProject": { + "titleSave": "保存项目", + "titleUpdate": "更新项目", + "nameLabel": "项目名称 *", + "namePlaceholder": "我的精彩项目", + "descriptionLabel": "描述", + "descriptionPlaceholder": "可选", + "save": "保存", + "update": "更新", + "saving": "保存中…", + "cancel": "取消", + "visibility": { + "public": "公开", + "publicHint": "任何拥有此链接的人都可以查看", + "private": "私有", + "privateHint": "仅您可见" + }, + "errors": { + "nameRequired": "项目名称必填。", + "unreachable": "无法连接到服务器。请检查网络后重试。", + "notAuth": "未登录。请登录后重试。", + "failedStatus": "保存失败 ({{status}})。" + } + }, + "share": { + "title": "分享项目", + "public": "公开", + "private": "私有", + "publicHint": "任何拥有此链接的人都可以查看本项目", + "privateHint": "仅您可见本项目", + "makePrivate": "设为私有", + "makePublic": "设为公开", + "copy": "复制", + "privateWarning": "此项目为私有。他人打开此链接将看到 403 错误。", + "close": "关闭" } } }