Skip to content

DOMAIN:CONTENT — CONTENT-DEVELOPER HANDOFF

OWNER: jouke, dinand, benjamin, joost
ALSO_USED_BY: floris, floor (Frontend), faye, sytske (Project Management)
UPDATED: 2026-03-24


HANDOFF:COPY_TO_CODEBASE

Methods (ranked by preference)

Method When Pros Cons
i18n JSON files in repo Default for all projects Version controlled, type-safe, CI-validated Needs developer to merge
TMS → repo sync (Crowdin/Lokalise) Multi-language projects Automated, translator-friendly Setup cost, monthly fee
Headless CMS (Strapi (FR), Payload (self-hosted)) Marketing/content-heavy pages Non-dev can edit, preview Runtime dependency, cost
Hardcoded strings Never Untranslatable, unreviewable

RULE: default to i18n JSON files — every project starts here
RULE: graduate to TMS when >2 target languages
RULE: use headless CMS only for marketing pages, landing pages, blog
RULE: NEVER hardcode user-visible strings — even for MVP

i18n JSON File Workflow

WORKFLOW:
1. Content lead creates/updates copy in shared document (Notion, Google Doc)
2. Content lead exports to i18n JSON format (or writes directly)
3. Content lead opens PR with JSON changes to messages/{locale}.json
4. Developer reviews for:
   - Valid JSON syntax
   - Correct key naming (follows conventions)
   - No broken ICU placeholders
   - String length within UI bounds
5. Content lead and developer co-approve
6. PR merges → deploys

RULE: content PRs are separate from code PRs — review independently
RULE: PR description includes screenshot of affected UI
RULE: CI validates JSON syntax + key completeness across all locales


HANDOFF:DYNAMIC_CONTENT

CMS Content Types

IF: content changes more than once per sprint
THEN: use headless CMS, not JSON files

IF: content is static (UI labels, errors, forms)
THEN: i18n JSON files, not CMS

IF: content is user-generated
THEN: database, not CMS or JSON

CMS Architecture

CONTENT FLOW:
CMS (Strapi (FR) / Payload (self-hosted) preferred. Contentful (US) secondary only.) → API → Next.js ISR/SSG → CDN

RULE: use ISR (Incremental Static Regeneration) — not client-side fetch
RULE: cache CMS responses — CDN for public, Redis for personalized
RULE: fallback content defined in code for CMS outage resilience
RULE: content model mirrors i18n key structure where possible

What Goes Where

Content Type Storage Owner
UI labels, buttons, forms i18n JSON Content lead
Error messages i18n JSON Content lead
Legal text (ToS, privacy) CMS or static page Compliance (julian)
Marketing copy CMS Client / Content lead
Blog posts CMS or MDX Client
In-app help / tooltips i18n JSON Content lead
Email templates CMS or template files Content lead
Push notification text i18n JSON or CMS Content lead

HANDOFF:COPY_REVIEW_IN_PRS

PR Review Checklist for Copy Changes

COPY PR CHECKLIST:
□ JSON is valid (CI check)
□ All locales have the new key (CI check)
□ Key follows naming convention ({feature}.{component}.{element})
□ Placeholders use ICU syntax, not string concatenation
□ Pluralization uses CLDR forms (not just one/other for non-English)
□ Copy matches approved text from content document
□ No truncation risk (flag strings >50 chars for mobile buttons)
□ Screenshot attached showing the copy in context
□ Accessibility: no color-only meaning, descriptive link text
□ No hardcoded strings added in same PR (code review)

Automation

TOOL: GitHub Action — i18n completeness check

# .github/workflows/i18n-check.yml
name: i18n Check
on: pull_request
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check i18n completeness
        run: |
          npx next-intl-lint
          # OR custom: compare keys across locale files
          node scripts/check-i18n-completeness.js

TOOL: Dire CLI — translation completeness
RUN: npx dire check --source messages/en.json --targets messages/nl.json messages/de.json

RULE: CI MUST fail if any locale is missing keys present in default locale
RULE: CI SHOULD warn if any string exceeds 150% of source length (UI overflow risk)


HANDOFF:CONTENT_STATUS_TRACKING

Status Labels

Status Meaning Who Sets It
draft Work in progress Content lead
review Ready for peer review Content lead
approved Reviewed and approved Reviewer (peer or client)
implemented Merged into codebase Developer
live Deployed to production CI/CD

RULE: developers only implement approved copy — never draft
RULE: content changes in draft or review status stay in shared doc, not in code
RULE: every content task in the project tracker has a status label

Handoff Tooling

Tool Use Case Integration
Figma + Frontitude Design-content sync Plugin exports to JSON
Notion Content collaboration Manual export to JSON
Google Docs Client-facing drafts Manual export to JSON
Crowdin Translation management GitHub sync via CLI/webhook
Lokalise Translation management GitHub sync via CLI/webhook

RULE: prefer tools with Git integration — reduces manual copy-paste
RULE: single source of truth for copy = the i18n JSON file in the repo (once implemented)
RULE: shared docs (Notion, Google Docs) are staging — code is production

ANTI_PATTERN: copy living in Figma that never makes it to code
FIX: content lead opens PR with copy — it's their deliverable, not the developer's

ANTI_PATTERN: developer writing copy because content lead hasn't delivered
FIX: block UI tasks on copy tasks in the DAG — no implementation without approved copy

ANTI_PATTERN: copy changes going directly to production without PR review
FIX: all copy changes go through PR with review checklist — no exceptions