feat: add Translation Management with approval workflow
All checks were successful
Deploy / deploy (push) Successful in 12s
All checks were successful
Deploy / deploy (push) Successful in 12s
- New Translations + TranslationTexts NocoDB tables (auto-created on restart) - Full CRUD: list, create, update, delete, bulk-delete translations - Translation texts per language (add/edit/delete inline) - Review flow: submit-review generates public token link - Public review page: shows source + all translations, approve/reject/revision - Email notifications to approvers (registered users) - Sidebar nav under Marketing category - Bilingual i18n (80+ keys in en.json and ar.json) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,8 +85,10 @@ const t = {
|
||||
// Types
|
||||
post: { en: 'post', ar: 'منشور' },
|
||||
artefact: { en: 'artefact', ar: 'قطعة إبداعية' },
|
||||
Post: { en: 'Post', ar: 'المنشور' },
|
||||
Artefact: { en: 'Artefact', ar: 'القطعة الإبداعية' },
|
||||
Post: { en: 'Post', ar: 'المنشور' },
|
||||
Artefact: { en: 'Artefact', ar: 'القطعة الإبداعية' },
|
||||
translation: { en: 'translation', ar: 'ترجمة' },
|
||||
Translation: { en: 'Translation', ar: 'الترجمة' },
|
||||
|
||||
// Generic
|
||||
view: { en: 'View', ar: 'عرض' },
|
||||
@@ -192,14 +194,14 @@ function notifyApproved({ type, record, approverName }) {
|
||||
getUser(creatorId).then(user => {
|
||||
if (!user) return;
|
||||
const l = user.lang;
|
||||
const typeLabel = tr(type === 'post' ? 'Post' : 'Artefact', l);
|
||||
const typeLabel = tr(type === 'post' ? 'Post' : type === 'translation' ? 'Translation' : 'Artefact', l);
|
||||
send({
|
||||
to: user.email, lang: l,
|
||||
subject: `${tr('approved', l)}: ${title}`,
|
||||
heading: tr('approvedHeading', l)(typeLabel),
|
||||
bodyHtml: `<p>${tr('approvedBody', l)(title, approverName || (l === 'ar' ? 'مراجع' : 'a reviewer'))}</p>`,
|
||||
ctaText: `${tr('view', l)} ${typeLabel}`,
|
||||
ctaUrl: `${APP_URL}/${type === 'post' ? 'posts' : 'artefacts'}`,
|
||||
ctaUrl: `${APP_URL}/${type === 'post' ? 'posts' : type === 'translation' ? 'translations' : 'artefacts'}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -213,7 +215,7 @@ function notifyRejected({ type, record, approverName, feedback }) {
|
||||
getUser(creatorId).then(user => {
|
||||
if (!user) return;
|
||||
const l = user.lang;
|
||||
const typeLabel = tr(type === 'post' ? 'Post' : 'Artefact', l);
|
||||
const typeLabel = tr(type === 'post' ? 'Post' : type === 'translation' ? 'Translation' : 'Artefact', l);
|
||||
send({
|
||||
to: user.email, lang: l,
|
||||
subject: `${tr('needsChanges', l)}: ${title}`,
|
||||
@@ -222,16 +224,18 @@ function notifyRejected({ type, record, approverName, feedback }) {
|
||||
<p>${tr('rejectedBody', l)(title, approverName || (l === 'ar' ? 'مراجع' : 'a reviewer'))}</p>
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${feedback}</blockquote>` : ''}`,
|
||||
ctaText: `${tr('view', l)} ${typeLabel}`,
|
||||
ctaUrl: `${APP_URL}/${type === 'post' ? 'posts' : 'artefacts'}`,
|
||||
ctaUrl: `${APP_URL}/${type === 'post' ? 'posts' : type === 'translation' ? 'translations' : 'artefacts'}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Revision requested (artefact) → notify creator
|
||||
function notifyRevisionRequested({ record, approverName, feedback }) {
|
||||
function notifyRevisionRequested({ type, record, approverName, feedback }) {
|
||||
const creatorId = record.created_by_user_id;
|
||||
if (!creatorId) return;
|
||||
const title = record.title || 'Untitled';
|
||||
const entityType = type === 'translation' ? 'Translation' : 'Artefact';
|
||||
const entityPath = type === 'translation' ? 'translations' : 'artefacts';
|
||||
|
||||
getUser(creatorId).then(user => {
|
||||
if (!user) return;
|
||||
@@ -243,8 +247,8 @@ function notifyRevisionRequested({ record, approverName, feedback }) {
|
||||
bodyHtml: `
|
||||
<p>${tr('revisionRequestedBody', l)(title, approverName || (l === 'ar' ? 'مراجع' : 'a reviewer'))}</p>
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${feedback}</blockquote>` : ''}`,
|
||||
ctaText: `${tr('view', l)} ${tr('Artefact', l)}`,
|
||||
ctaUrl: `${APP_URL}/artefacts`,
|
||||
ctaText: `${tr('view', l)} ${tr(entityType, l)}`,
|
||||
ctaUrl: `${APP_URL}/${entityPath}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user