feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)

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.
This commit is contained in:
David Montero Crespo 2026-05-09 03:12:52 -03:00
parent 7f0ac2a74c
commit fa4f3d6e80
12 changed files with 442 additions and 41 deletions

View File

@ -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<LoginPromptModalProps> = ({ onClose }) => (
<div style={styles.overlay} onClick={onClose}>
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
<h2 style={styles.title}>Sign in to save your project</h2>
<p style={styles.body}>Create a free account to save and share your projects.</p>
<div style={styles.actions}>
<Link to="/login" style={styles.primaryBtn}>
Sign in
</Link>
<Link to="/register" style={styles.secondaryBtn}>
Create account
</Link>
<button onClick={onClose} style={styles.cancelBtn}>
Cancel
</button>
export const LoginPromptModal: React.FC<LoginPromptModalProps> = ({ onClose }) => {
const { t } = useTranslation();
const localize = useLocalizedHref();
return (
<div style={styles.overlay} onClick={onClose}>
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
<h2 style={styles.title}>{t('editor.loginPrompt.title')}</h2>
<p style={styles.body}>{t('editor.loginPrompt.body')}</p>
<div style={styles.actions}>
<Link to={localize('/login')} style={styles.primaryBtn}>
{t('header.auth.signIn')}
</Link>
<Link to={localize('/register')} style={styles.secondaryBtn}>
{t('editor.loginPrompt.createAccount')}
</Link>
<button onClick={onClose} style={styles.cancelBtn}>
{t('editor.loginPrompt.cancel')}
</button>
</div>
</div>
</div>
</div>
);
);
};
const styles: Record<string, React.CSSProperties> = {
overlay: {

View File

@ -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<SaveProjectModalProps> = ({ 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<SaveProjectModalProps> = ({ 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<SaveProjectModalProps> = ({ 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<SaveProjectModalProps> = ({ onClose }) =
return (
<div style={styles.overlay} onClick={onClose}>
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
<h2 style={styles.title}>{isUpdate ? 'Update project' : 'Save project'}</h2>
<h2 style={styles.title}>
{isUpdate ? t('editor.saveProject.titleUpdate') : t('editor.saveProject.titleSave')}
</h2>
{error && <div style={styles.error}>{error}</div>}
<form onSubmit={handleSave} style={styles.form}>
<label style={styles.label}>Project name *</label>
<label style={styles.label}>{t('editor.saveProject.nameLabel')}</label>
<input
type="text"
value={name}
@ -94,16 +101,16 @@ export const SaveProjectModal: React.FC<SaveProjectModalProps> = ({ onClose }) =
required
style={styles.input}
autoFocus
placeholder="My awesome project"
placeholder={t('editor.saveProject.namePlaceholder')}
/>
<label style={styles.label}>Description</label>
<label style={styles.label}>{t('editor.saveProject.descriptionLabel')}</label>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
style={styles.input}
placeholder="Optional"
placeholder={t('editor.saveProject.descriptionPlaceholder')}
/>
<div
@ -147,10 +154,14 @@ export const SaveProjectModal: React.FC<SaveProjectModalProps> = ({ onClose }) =
<div
style={{ color: isPublic ? '#4ade80' : '#f59e0b', fontSize: 13, fontWeight: 600 }}
>
{isPublic ? 'Public' : 'Private'}
{isPublic
? t('editor.saveProject.visibility.public')
: t('editor.saveProject.visibility.private')}
</div>
<div style={{ color: '#888', fontSize: 11 }}>
{isPublic ? 'Anyone with the link can view' : 'Only you can see this'}
{isPublic
? t('editor.saveProject.visibility.publicHint')
: t('editor.saveProject.visibility.privateHint')}
</div>
</div>
</div>
@ -158,10 +169,14 @@ export const SaveProjectModal: React.FC<SaveProjectModalProps> = ({ onClose }) =
<div style={styles.actions}>
<button type="submit" disabled={saving} style={styles.saveBtn}>
{saving ? 'Saving…' : isUpdate ? 'Update' : 'Save'}
{saving
? t('editor.saveProject.saving')
: isUpdate
? t('editor.saveProject.update')
: t('editor.saveProject.save')}
</button>
<button type="button" onClick={onClose} style={styles.cancelBtn}>
Cancel
{t('editor.saveProject.cancel')}
</button>
</div>
</form>

View File

@ -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<ShareModalProps> = ({ 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<ShareModalProps> = ({ onClose }) => {
return createPortal(
<div style={styles.overlay} onClick={onClose}>
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
<h2 style={styles.title}>Share project</h2>
<h2 style={styles.title}>{t('editor.share.title')}</h2>
{/* Visibility toggle */}
<div style={styles.visibilityRow}>
@ -83,13 +85,11 @@ export const ShareModal: React.FC<ShareModalProps> = ({ onClose }) => {
<span
style={{ color: isPublic ? '#4ade80' : '#f59e0b', fontWeight: 600, fontSize: 13 }}
>
{isPublic ? 'Public' : 'Private'}
{isPublic ? t('editor.share.public') : t('editor.share.private')}
</span>
</span>
<span style={{ color: '#888', fontSize: 12 }}>
{isPublic
? 'Anyone with the link can view this project'
: 'Only you can see this project'}
{isPublic ? t('editor.share.publicHint') : t('editor.share.privateHint')}
</span>
</div>
<button
@ -100,7 +100,11 @@ export const ShareModal: React.FC<ShareModalProps> = ({ onClose }) => {
opacity: toggling ? 0.5 : 1,
}}
>
{toggling ? '...' : isPublic ? 'Make private' : 'Make public'}
{toggling
? '...'
: isPublic
? t('editor.share.makePrivate')
: t('editor.share.makePublic')}
</button>
</div>
@ -128,20 +132,18 @@ export const ShareModal: React.FC<ShareModalProps> = ({ onClose }) => {
<polyline points="20 6 9 17 4 12" />
</svg>
) : (
'Copy'
t('editor.share.copy')
)}
</button>
</div>
{!isPublic && (
<div style={styles.warning}>
This project is private. Others will see a 403 error when opening this link.
</div>
<div style={styles.warning}>{t('editor.share.privateWarning')}</div>
)}
<div style={styles.actions}>
<button onClick={onClose} style={styles.closeBtn}>
Close
{t('editor.share.close')}
</button>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": "閉じる"
}
}
}

View File

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

View File

@ -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": "Закрыть"
}
}
}

View File

@ -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": "关闭"
}
}
}