Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

8.8 kB Ruw bestand
import { formatCount, type Locale } from '@wellformed/shared';
import type { MessageCatalog } from './entry';

/**
 * The marketing page's fixed chrome. The four selling points in the middle are
 * not here on purpose: they come from the voice pool, measured copy with fixed
 * ids — see `shared/voice.ts`.
 */
export const marketing = {
  /* What search results and link previews show. The title repeats the
     headline because the headline is the argument; the description compresses
     the lede to what fits a result snippet. */
  'marketing.metaTitle': {
    en: 'Wellformed — the spreadsheet that can’t quietly go wrong',
    nl: 'Wellformed — de spreadsheet die niet stilletjes fout kan gaan',
  },
  'marketing.metaDescription': {
    en:
      'A spreadsheet with a real database underneath: every column has a type, every link points ' +
      'at a row that exists, and mistakes are refused the moment somebody makes them.',
    nl:
      'Een spreadsheet met een echte database eronder: elke kolom heeft een type, elke koppeling ' +
      'wijst naar een rij die echt bestaat, en fouten worden geweigerd op het moment dat iemand ' +
      'ze maakt.',
  },

  'marketing.pricing': { en: 'Pricing', nl: 'Prijzen' },
  'marketing.yourWorkspaces': { en: 'Your workspaces', nl: 'Je werkruimtes' },
  'marketing.startFree': { en: 'Start free', nl: 'Gratis beginnen' },

  'marketing.strapline': {
    en: 'A spreadsheet with a database underneath',
    nl: 'Een spreadsheet met een database eronder',
  },
  'marketing.headline': {
    en: 'The spreadsheet that can’t quietly go wrong',
    nl: 'De spreadsheet die niet stilletjes fout kan gaan',
  },
  'marketing.lede': {
    en: 'You still click a cell, type, and press Enter. What is different sits underneath: every column has a type, every link points at a row that really exists, and required means required. So the mistakes that used to sit in the file for months — a word where a price goes, an order pointing at a customer who was deleted, a blank nobody noticed — are refused at the moment somebody makes them.',
    nl: 'Je klikt nog steeds op een cel, typt, en drukt op Enter. Het verschil zit eronder: elke kolom heeft een type, elke koppeling wijst naar een rij die echt bestaat, en verplicht betekent verplicht. Dus de fouten die vroeger maandenlang in het bestand bleven zitten — een woord waar een prijs hoort, een order die wijst naar een verwijderde klant, een leeg vakje dat niemand opviel — worden geweigerd op het moment dat iemand ze maakt.',
  },
  'marketing.moveOver': { en: 'Move a spreadsheet over', nl: 'Zet een spreadsheet over' },
  'marketing.openDemo': { en: 'Open the demo — no account', nl: 'Open de demo — zonder account' },
  'marketing.freeToStart': {
    en: (tables: string, rows: string) =>
      `Free to start, no card — ${tables} and ${rows}, enough to move a real spreadsheet over and see whether it holds.`,
    nl: (tables: string, rows: string) =>
      `Gratis om te beginnen, geen creditcard — ${tables} en ${rows}, genoeg om een echte spreadsheet over te zetten en te zien of het standhoudt.`,
  },
  'marketing.inviteOnly': {
    en: 'This installation is invitation-only. Ask someone here to invite you.',
    nl: 'Deze installatie is alleen op uitnodiging. Vraag iemand hier om je uit te nodigen.',
  },

  'marketing.proofLabel': {
    en: 'A workspace, a few weeks in',
    nl: 'Een werkruimte, een paar weken verder',
  },
  'marketing.proofAlt': {
    en: 'An orders table in Wellformed: each row links to a customer and a product, a Total column adds itself up from the linked price — the strip above the grid shows the formula behind the selected total — saved views and a workflow rule sit in the sidebar, and the strip along the bottom shows the SQL statement behind the screen.',
    nl: 'Een ordertabel in Wellformed: elke rij is gekoppeld aan een klant en een product, een totaalkolom telt zichzelf op uit de gekoppelde prijs — de strook boven het raster toont de formule achter het geselecteerde totaal — opgeslagen weergaven en een workflowregel staan in de zijbalk, en de strook onderaan toont het SQL-statement achter het scherm.',
  },
  'marketing.proofHint': {
    en: 'Orders linked to customers and products, a total that works itself out, and the saved views down the side. The dark strip is the part nobody else shows you: the exact statement behind what you are looking at. You never have to read it — it is there so no number on this screen has to be taken on trust.',
    nl: 'Orders gekoppeld aan klanten en producten, een totaal dat zichzelf uitrekent, en de opgeslagen weergaven aan de zijkant. De donkere strook is het deel dat niemand anders je laat zien: het exacte statement achter wat je bekijkt. Je hoeft het nooit te lezen — het staat er zodat je geen enkel getal op dit scherm op gezag hoeft aan te nemen.',
  },

  'marketing.pricingTitle': {
    en: 'Two prices, and that is the whole price list.',
    nl: 'Twee prijzen, en dat is de hele prijslijst.',
  },
  'marketing.pricingHint': {
    en: 'Per workspace, not per person. Add everyone you work with on either tier.',
    nl: 'Per werkruimte, niet per persoon. Voeg op beide niveaus iedereen toe met wie je werkt.',
  },
  'marketing.perMonth': { en: '/month', nl: '/maand' },
  'marketing.limitRows': {
    en: (limit: number | null, locale: Locale) =>
      limit === null ? 'unlimited rows' : `${formatCount(limit, locale)} rows`,
    nl: (limit: number | null, locale: Locale) =>
      limit === null ? 'onbeperkt aantal rijen' : `${formatCount(limit, locale)} rijen`,
  },
  'marketing.limitTables': {
    en: (limit: number | null, locale: Locale) =>
      limit === null ? 'unlimited tables' : `${formatCount(limit, locale)} tables`,
    nl: (limit: number | null, locale: Locale) =>
      limit === null ? 'onbeperkt aantal tabellen' : `${formatCount(limit, locale)} tabellen`,
  },
  'marketing.startFreeUpgrade': {
    en: 'Start free, upgrade later',
    nl: 'Gratis beginnen, later upgraden',
  },
  'marketing.vatHint': {
    en: 'Prices exclude VAT. A workspace that reaches its limit stops taking new rows and new tables — nothing is locked, nothing is deleted, and you can still export all of it.',
    nl: 'Prijzen zijn exclusief btw. Een werkruimte die zijn limiet bereikt neemt geen nieuwe rijen en tabellen meer aan — niets gaat op slot, niets wordt verwijderd, en je kunt nog steeds alles exporteren.',
  },

  'marketing.closer': {
    en: 'Move one real table over and see what it refuses. That takes about ten minutes, and it settles the question better than any page can.',
    nl: 'Zet één echte tabel over en kijk wat hij weigert. Dat kost een minuut of tien, en het beantwoordt de vraag beter dan welke pagina ook.',
  },
  'marketing.demo': { en: 'Demo', nl: 'Demo' },

  /*
   * The pricing-card copy. The *numbers* stay in shared/plans.ts so the page
   * and the server's enforcement cannot drift apart; the words about them live
   * here with every other word the client speaks. The plan names — Free, Team —
   * are product names and stay as they are.
   */
  'marketing.plan.free.blurb': {
    en: 'Enough to move a real spreadsheet over and see whether it holds.',
    nl: 'Genoeg om een echte spreadsheet over te zetten en te zien of het standhoudt.',
  },
  'marketing.plan.free.include1': {
    en: 'Everything in the engine: links, formulas, rollups, views',
    nl: 'Alles in de engine: koppelingen, formules, rollups, weergaven',
  },
  'marketing.plan.free.include2': {
    en: 'Unlimited people, any mix of admins, editors and viewers',
    nl: 'Onbeperkt mensen, elke mix van beheerders, bewerkers en kijkers',
  },
  'marketing.plan.free.include3': {
    en: 'The full audit trail',
    nl: 'De volledige audittrail',
  },
  'marketing.plan.free.include4': {
    en: 'Export the whole workspace as a SQLite file, whenever you like',
    nl: 'Exporteer de hele werkruimte als SQLite-bestand, wanneer je maar wilt',
  },
  'marketing.plan.team.blurb': {
    en: 'One flat price per workspace. Add everyone you work with.',
    nl: 'Eén vaste prijs per werkruimte. Voeg iedereen toe met wie je werkt.',
  },
  'marketing.plan.team.include1': {
    en: 'Everything in Free, without the ceiling',
    nl: 'Alles van Free, zonder het plafond',
  },
  'marketing.plan.team.include2': {
    en: 'As many tables as the work needs',
    nl: 'Zoveel tabellen als het werk nodig heeft',
  },
  'marketing.plan.team.include3': {
    en: (rows: number, locale: Locale) => `${formatCount(rows, locale)} rows per workspace`,
    nl: (rows: number, locale: Locale) => `${formatCount(rows, locale)} rijen per werkruimte`,
  },
  'marketing.plan.team.include4': {
    en: 'Still unlimited people — there is no per-seat price to plan around',
    nl: 'Nog steeds onbeperkt mensen — er is geen prijs per gebruiker om rekening mee te houden',
  },
} as const satisfies MessageCatalog;