+
@@ -24,8 +24,8 @@ export function getEmailTemplate(title: string, content: string, actionLink?: st
${content}
- ${actionLink ? `
` : ''}
-
+ ${actionLink ? `
` : ''}
+
diff --git a/memento-note/lib/i18n/LanguageProvider.tsx b/memento-note/lib/i18n/LanguageProvider.tsx
index 953bbf8..828eb5e 100644
--- a/memento-note/lib/i18n/LanguageProvider.tsx
+++ b/memento-note/lib/i18n/LanguageProvider.tsx
@@ -50,11 +50,16 @@ export function LanguageProvider({ children, initialLanguage = 'en', initialTran
const isFirstRender = useRef(true)
- // Load saved preference from localStorage AFTER hydration
+ // Load saved preference from cookie only (explicit picker). localStorage
+ // without cookie used to override note-based detection with a stale 'en'.
useEffect(() => {
- const saved = localStorage.getItem('user-language') as SupportedLanguage
- if (saved && SUPPORTED_LANGS.includes(saved) && saved !== initialLanguage) {
- setLanguageState(saved)
+ const cookie = document.cookie
+ .split(';')
+ .map(s => s.trim())
+ .find(s => s.startsWith('user-language='))
+ ?.split('=')[1] as SupportedLanguage | undefined
+ if (cookie && SUPPORTED_LANGS.includes(cookie) && cookie !== initialLanguage) {
+ setLanguageState(cookie)
}
}, [initialLanguage])
diff --git a/memento-note/lib/interactive-demo/schema.ts b/memento-note/lib/interactive-demo/schema.ts
index 893ab5b..732e734 100644
--- a/memento-note/lib/interactive-demo/schema.ts
+++ b/memento-note/lib/interactive-demo/schema.ts
@@ -51,7 +51,7 @@ const svgEdgeSchema = z.object({
from: z.string().min(1),
to: z.string().min(1),
style: z.enum(['solid', 'dashed']).optional(),
- weight: z.number().optional(),
+ weight: z.number().min(0).max(5).optional(),
intent: intentSchema,
})
@@ -73,8 +73,8 @@ const chartPayloadSchema = z.object({
})
const heatmapPayloadSchema = z.object({
- rows: z.number().int().positive(),
- cols: z.number().int().positive(),
+ rows: z.number().int().positive().max(50),
+ cols: z.number().int().positive().max(50),
values: z.array(z.array(z.number())),
triangular: z.enum(['lower', 'upper', 'none']).optional(),
rowLabels: z.array(z.string()).optional(),
diff --git a/memento-note/lib/interactive-page/constants.ts b/memento-note/lib/interactive-page/constants.ts
index 578488d..c175545 100644
--- a/memento-note/lib/interactive-page/constants.ts
+++ b/memento-note/lib/interactive-page/constants.ts
@@ -8,7 +8,7 @@ export const INTERACTIVE_PAGE_CAPS = {
maxDemosPerPage: 5,
maxSimsPerPage: 3,
maxOverviewCards: 4,
- minOverviewCards: 2,
+ minOverviewCards: 3,
maxStatsItems: 5,
minStatsItems: 2,
maxJsonBytes: 128 * 1024,
@@ -30,8 +30,14 @@ export const PAGE_BLOCK_TYPES = [
'table',
'image',
'sim',
+ 'steps',
] as const
+export const STEPS_CAPS = {
+ minSteps: 2,
+ maxSteps: 12,
+} as const
+
export const CALLOUT_KINDS = [
'definition',
'warning',
@@ -69,6 +75,8 @@ export const PAGE_HUMAN_STRING_KEYS = [
'intro',
'xLabel',
'yLabel',
+ // steps blocks
+ 'rule',
// inherited from demos (speak etc. scanned via demo validator)
'speak',
'text',
diff --git a/memento-note/lib/interactive-page/fixtures/thermo-page.json b/memento-note/lib/interactive-page/fixtures/thermo-page.json
index 425617d..4884021 100644
--- a/memento-note/lib/interactive-page/fixtures/thermo-page.json
+++ b/memento-note/lib/interactive-page/fixtures/thermo-page.json
@@ -63,6 +63,33 @@
"simId": "ts-diagram"
},
"caption": "Le même cycle sur le diagramme T–s : les aires sont les chaleurs échangées."
+ },
+ {
+ "type": "steps",
+ "title": "Le COP frigorifique, dérivé pas à pas",
+ "steps": [
+ {
+ "tex": "\\eta_{\\text{Carnot}} = 1 - \\frac{T_c}{T_h}",
+ "rule": "Point de départ — rendement de Carnot",
+ "speak": "Le rendement maximal d'un moteur entre $T_h$ et $T_c$ ne dépend que des températures."
+ },
+ {
+ "tex": "\\mathrm{COP}_{\\text{PAC}} = \\frac{1}{\\eta_{\\text{Carnot}}} = \\frac{T_h}{T_h - T_c}",
+ "rule": "Inversion — pompe à chaleur",
+ "speak": "La pompe à chaleur est l'inverse du moteur : son COP est l'inverse du rendement."
+ },
+ {
+ "tex": "\\mathrm{COP}_{\\text{frigo}} = \\mathrm{COP}_{\\text{PAC}} - 1 = \\frac{T_c}{T_h - T_c}",
+ "rule": "Soustraction de 1 — réfrigérateur",
+ "speak": "Le frigo ne compte que la chaleur utile $Q_c$ : on retire 1 au COP de la PAC."
+ },
+ {
+ "tex": "\\mathrm{COP}_{\\text{frigo}} = \\frac{260}{300 - 260} = 6{,}5",
+ "rule": "Application numérique",
+ "speak": "Avec $T_c = 260$ K et $T_h = 300$ K : le COP maximal vaut 6,5."
+ }
+ ],
+ "caption": "Chaque ligne découle de la précédente — la règle appliquée est en marge."
}
]
},
diff --git a/memento-note/lib/interactive-page/index.ts b/memento-note/lib/interactive-page/index.ts
index 4e5c27d..5d9ab09 100644
--- a/memento-note/lib/interactive-page/index.ts
+++ b/memento-note/lib/interactive-page/index.ts
@@ -4,6 +4,7 @@ export {
PAGE_BLOCK_TYPES,
CALLOUT_KINDS,
PAGE_HUMAN_STRING_KEYS,
+ STEPS_CAPS,
isPageHumanStringKey,
} from './constants'
export { pageSpecV1Schema, pageBlockSchema } from './schema'
@@ -22,4 +23,6 @@ export type {
CatalogSimRef,
GenericFormulaSim,
SimBlock,
+ StepsBlock,
+ DerivationStep,
} from './types'
diff --git a/memento-note/lib/interactive-page/normalize.ts b/memento-note/lib/interactive-page/normalize.ts
index 389fbb7..b6d85f3 100644
--- a/memento-note/lib/interactive-page/normalize.ts
+++ b/memento-note/lib/interactive-page/normalize.ts
@@ -189,6 +189,31 @@ function normalizeBlock(
return out
}
+ if (type === 'steps' || type === 'derivation' || type === 'walkthrough' || type === 'solution' || type === 'proof') {
+ const rawSteps = Array.isArray(obj.steps) ? obj.steps : []
+ const steps = rawSteps
+ .map((st) => {
+ const r = asRecord(st)
+ if (!r) return null
+ const tex = asString(r.tex) || asString(r.latex) || asString(r.equation) || asString(r.math)
+ if (!tex) return null
+ const out: Record
= { tex }
+ const rule = asString(r.rule) || asString(r.transform) || asString(r.action) || asString(r.operation)
+ const speak = asString(r.speak) || asString(r.note) || asString(r.comment)
+ if (rule) out.rule = rule
+ if (speak) out.speak = speak
+ return out
+ })
+ .filter(Boolean)
+ if (steps.length < 2) return null
+ const out: Record = { type: 'steps', steps }
+ const title = asString(obj.title)
+ if (title) out.title = title
+ const caption = asString(obj.caption)
+ if (caption) out.caption = caption
+ return out
+ }
+
return null
}
diff --git a/memento-note/lib/interactive-page/schema.ts b/memento-note/lib/interactive-page/schema.ts
index a77f97e..11cc48c 100644
--- a/memento-note/lib/interactive-page/schema.ts
+++ b/memento-note/lib/interactive-page/schema.ts
@@ -7,7 +7,9 @@ import {
INTERACTIVE_PAGE_SCHEMA_VERSION,
PAGE_BLOCK_TYPES,
SIM_CAPS,
+ STEPS_CAPS,
} from './constants'
+import { isValidSimParamId } from './sim-eval'
const intentSchema = z.enum(INTENT_IDS).optional()
@@ -90,6 +92,9 @@ const imageBlock = z.object({
const simParamIdSchema = z
.string()
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'Invalid sim identifier')
+ .refine(isValidSimParamId, {
+ message: 'Identifier collides with a reserved constant/function',
+ })
const genericSimParamSchema = z.object({
id: simParamIdSchema,
@@ -150,6 +155,22 @@ const simBlock = z.object({
caption: z.string().optional(),
})
+const stepsBlock = z.object({
+ type: z.literal('steps'),
+ title: z.string().optional(),
+ steps: z
+ .array(
+ z.object({
+ tex: z.string().min(1),
+ rule: z.string().optional(),
+ speak: z.string().optional(),
+ })
+ )
+ .min(STEPS_CAPS.minSteps)
+ .max(STEPS_CAPS.maxSteps),
+ caption: z.string().optional(),
+})
+
export const pageBlockSchema = z.discriminatedUnion('type', [
proseBlock,
formulaBlock,
@@ -160,6 +181,7 @@ export const pageBlockSchema = z.discriminatedUnion('type', [
tableBlock,
imageBlock,
simBlock,
+ stepsBlock,
])
const sectionSchema = z.object({
@@ -199,7 +221,7 @@ export const pageSpecV1Schema = z.object({
overview: overviewSchema.optional(),
sections: z
.array(sectionSchema)
- .min(1)
+ .min(2)
.max(INTERACTIVE_PAGE_CAPS.maxSections),
footer: z.string().optional(),
})
diff --git a/memento-note/lib/interactive-page/types.ts b/memento-note/lib/interactive-page/types.ts
index ad800df..59f3a2c 100644
--- a/memento-note/lib/interactive-page/types.ts
+++ b/memento-note/lib/interactive-page/types.ts
@@ -115,6 +115,27 @@ export type SimBlock = {
caption?: string
}
+/**
+ * Step-by-step derivation (Symbolab/Khan style): equation states revealed
+ * line by line with the transformation rule used at each step. Fully
+ * generic — the LLM writes KaTeX + rules, the app renders; no drawing.
+ */
+export type DerivationStep = {
+ /** KaTeX of the equation state at this step. */
+ tex: string
+ /** Transformation rule applied to reach this state (e.g. "on sépare les variables"). */
+ rule?: string
+ /** Narration for the Play/Step player (falls back to rule). */
+ speak?: string
+}
+
+export type StepsBlock = {
+ type: 'steps'
+ title?: string
+ steps: DerivationStep[]
+ caption?: string
+}
+
export type PageBlock =
| ProseBlock
| FormulaBlock
@@ -125,6 +146,7 @@ export type PageBlock =
| TableBlock
| ImageBlock
| SimBlock
+ | StepsBlock
export type PageSection = {
id: string
diff --git a/memento-note/lib/interactive-page/validate.ts b/memento-note/lib/interactive-page/validate.ts
index a4959c1..066777b 100644
--- a/memento-note/lib/interactive-page/validate.ts
+++ b/memento-note/lib/interactive-page/validate.ts
@@ -6,7 +6,7 @@ import {
isPageHumanStringKey,
} from './constants'
import { pageSpecV1Schema } from './schema'
-import { validateSimExprRefs } from './sim-eval'
+import { validateSimExprRefs, isValidSimParamId } from './sim-eval'
import type {
PageBlock,
PageSpecV1,
@@ -79,6 +79,13 @@ function validateSim(
if (sim.simId === 'generic-formula') {
const generic = sim as Extract
const paramIds = new Set(generic.params.map((p) => p.id))
+ if (paramIds.size !== generic.params.length) {
+ out.push(issue('sim_duplicate_id', `${path}.params`, 'Duplicate param id'))
+ }
+ const computedIds = new Set(generic.computed.map((c) => c.id))
+ if (computedIds.size !== generic.computed.length) {
+ out.push(issue('sim_duplicate_id', `${path}.computed`, 'Duplicate computed id'))
+ }
for (const p of generic.params) {
if (p.min >= p.max) {
out.push(issue('sim_param_range', `${path}.params`, `Param "${p.id}": min >= max`))
diff --git a/memento-note/locales/ar.json b/memento-note/locales/ar.json
index 9df055f..dc2fa8b 100644
--- a/memento-note/locales/ar.json
+++ b/memento-note/locales/ar.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — الخصوصية · الشروط",
"sessionExpired": "يتم إنشاء موقعك مع التنقل وجدول المحتويات",
"welcomeBack": "مرحبًا بعودتك",
- "welcomeBackSubtitle": "أدخل بيانات الاعتماد للوصول إلى ملاحظاتك."
+ "welcomeBackSubtitle": "أدخل بيانات الاعتماد للوصول إلى ملاحظاتك.",
+ "checkEmailTitle": "تحقق من بريدك الإلكتروني",
+ "checkEmailDescription": "أرسلنا رابط تأكيد إلى {email}. افتحه لتفعيل حسابك قبل تسجيل الدخول.",
+ "checkEmailDescriptionGeneric": "أرسلنا رابط تأكيد إلى بريدك. افتحه لتفعيل حسابك قبل تسجيل الدخول.",
+ "resendVerification": "إعادة إرسال رسالة التأكيد",
+ "verifyResent": "تم إرسال رسالة التأكيد. تحقق من صندوق الوارد.",
+ "verifyResendFailed": "تعذّر إرسال رسالة التأكيد. حاول لاحقًا.",
+ "verifyMissingEmail": "أدخل عنوان بريدك الإلكتروني.",
+ "verifyLoading": "جارٍ تأكيد بريدك…",
+ "verifySuccessTitle": "تم تأكيد البريد",
+ "verifySuccessDescription": "حسابك جاهز. يمكنك تسجيل الدخول الآن.",
+ "verifyExpiredTitle": "انتهت صلاحية الرابط",
+ "verifyExpiredDescription": "انتهت صلاحية رابط التأكيد. اطلب رابطًا جديدًا.",
+ "verifyInvalidTitle": "رابط غير صالح",
+ "verifyInvalidDescription": "رابط التأكيد غير صالح أو سبق استخدامه.",
+ "emailNotVerified": "يرجى تأكيد بريدك قبل تسجيل الدخول.",
+ "emailVerifiedBanner": "تم تأكيد البريد. يمكنك تسجيل الدخول الآن.",
+ "invalidCredentials": "البريد أو كلمة المرور غير صحيحة."
},
"sidebar": {
"notes": "الملاحظات",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "حزمة مكثفة",
"buyPack": "شراء",
"packCheckoutSuccess": "تمت إضافة حزمة الأرصدة إلى رصيدك!",
- "packCheckoutFailed": "تعذّر بدء الشراء. تحقق من إعدادات Stripe أو أعد المحاولة."
+ "packCheckoutFailed": "تعذّر بدء الشراء. تحقق من إعدادات Stripe أو أعد المحاولة.",
+ "startTrialCta": "جرّب مجانًا لمدة {days} أيام",
+ "trialFeature": "تجربة مجانية لمدة {days} أيام (بطاقة مطلوبة)",
+ "trialEndsOn": "تنتهي فترتك التجريبية المجانية في {date}. سيتم تحصيل الرسوم تلقائيًا بعد ذلك.",
+ "trialEndsLabel": "نهاية التجربة"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "دعم مخصص",
"feature5": "إعداد مباشر"
},
- "basicPrice": "مجاني"
+ "basicPrice": "مجاني",
+ "savePercent": "وفّر حوالي 17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "حسب الطلب",
+ "trialBadge": "تجربة مجانية لمدة {days} أيام",
+ "trialFeature": "تجربة مجانية لمدة {days} أيام (بطاقة مطلوبة)",
+ "trialCta": "جرّب مجانًا لمدة {days} أيام"
},
"cta": {
"title": "توقف عن فقدان أفضل أفكارك.",
diff --git a/memento-note/locales/de.json b/memento-note/locales/de.json
index 82d4a2a..c62153d 100644
--- a/memento-note/locales/de.json
+++ b/memento-note/locales/de.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Datenschutz · AGB",
"sessionExpired": "Ihre Seite wird mit Navigation und Inhaltsverzeichnis generiert",
"welcomeBack": "Willkommen zurück",
- "welcomeBackSubtitle": "Geben Sie Ihre Anmeldedaten ein, um auf Ihre Notizen zuzugreifen."
+ "welcomeBackSubtitle": "Geben Sie Ihre Anmeldedaten ein, um auf Ihre Notizen zuzugreifen.",
+ "checkEmailTitle": "E-Mail prüfen",
+ "checkEmailDescription": "Wir haben einen Bestätigungslink an {email} gesendet. Öffnen Sie ihn, um Ihr Konto zu aktivieren.",
+ "checkEmailDescriptionGeneric": "Wir haben einen Bestätigungslink an Ihre E-Mail gesendet. Öffnen Sie ihn, um Ihr Konto zu aktivieren.",
+ "resendVerification": "Bestätigungs-E-Mail erneut senden",
+ "verifyResent": "Bestätigungs-E-Mail gesendet. Prüfen Sie Ihren Posteingang.",
+ "verifyResendFailed": "Bestätigungs-E-Mail konnte nicht gesendet werden. Später erneut versuchen.",
+ "verifyMissingEmail": "Geben Sie Ihre E-Mail-Adresse ein.",
+ "verifyLoading": "E-Mail wird bestätigt…",
+ "verifySuccessTitle": "E-Mail bestätigt",
+ "verifySuccessDescription": "Ihr Konto ist bereit. Sie können sich jetzt anmelden.",
+ "verifyExpiredTitle": "Link abgelaufen",
+ "verifyExpiredDescription": "Dieser Bestätigungslink ist abgelaufen. Fordern Sie einen neuen an.",
+ "verifyInvalidTitle": "Ungültiger Link",
+ "verifyInvalidDescription": "Dieser Bestätigungslink ist ungültig oder wurde bereits verwendet.",
+ "emailNotVerified": "Bitte bestätigen Sie Ihre E-Mail, bevor Sie sich anmelden.",
+ "emailVerifiedBanner": "E-Mail bestätigt. Sie können sich jetzt anmelden.",
+ "invalidCredentials": "Ungültige E-Mail oder Passwort."
},
"sidebar": {
"notes": "Notizen",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Power-Paket",
"buyPack": "Kaufen",
"packCheckoutSuccess": "Credit-Paket Ihrem Guthaben hinzugefügt!",
- "packCheckoutFailed": "Paketkauf fehlgeschlagen. Stripe-Konfiguration prüfen oder erneut versuchen."
+ "packCheckoutFailed": "Paketkauf fehlgeschlagen. Stripe-Konfiguration prüfen oder erneut versuchen.",
+ "startTrialCta": "{days} Tage kostenlos starten",
+ "trialFeature": "{days} Tage gratis testen (Karte erforderlich)",
+ "trialEndsOn": "Ihre kostenlose Testphase endet am {date}. Danach werden Sie automatisch belastet.",
+ "trialEndsLabel": "Testende"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Dedizierter Support",
"feature5": "Live-Onboarding"
},
- "basicPrice": "Kostenlos"
+ "basicPrice": "Kostenlos",
+ "savePercent": "~17% sparen",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "Individuell",
+ "trialBadge": "{days} Tage gratis testen",
+ "trialFeature": "{days} Tage gratis testen (Karte erforderlich)",
+ "trialCta": "{days} Tage kostenlos starten"
},
"cta": {
"title": "Hören Sie auf, Ihre besten Ideen zu verlieren.",
diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json
index ab7a265..5139d48 100644
--- a/memento-note/locales/en.json
+++ b/memento-note/locales/en.json
@@ -321,6 +321,7 @@
"switchType": "Switch to {type}",
"saveNow": "Save now",
"backToCollection": "Back to collection",
+ "backToDashboard": "Back to dashboard",
"markdownEditingTitle": "Return to editing",
"markdownPreviewTitle": "Preview",
"brainstormThisIdea": "Brainstorm this idea",
@@ -4288,6 +4289,8 @@
"toReview": "To review",
"allCaughtUp": "All caught up.",
"toOrganize": "to organize",
+ "inboxSeeAll": "See all {count}",
+ "inboxEmpty": "Inbox is empty.",
"review": "Review",
"cardsDue": "cards due",
"reminders": "Reminders",
diff --git a/memento-note/locales/es.json b/memento-note/locales/es.json
index de588f4..4d50d9d 100644
--- a/memento-note/locales/es.json
+++ b/memento-note/locales/es.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Privacidad · Términos",
"sessionExpired": "Tu sitio se genera con navegación y tabla de contenidos",
"welcomeBack": "Bienvenido de nuevo",
- "welcomeBackSubtitle": "Introduce tus credenciales para acceder a tus notas."
+ "welcomeBackSubtitle": "Introduce tus credenciales para acceder a tus notas.",
+ "checkEmailTitle": "Revisa tu correo",
+ "checkEmailDescription": "Enviamos un enlace de confirmación a {email}. Ábrelo para activar tu cuenta antes de iniciar sesión.",
+ "checkEmailDescriptionGeneric": "Enviamos un enlace de confirmación a tu correo. Ábrelo para activar tu cuenta antes de iniciar sesión.",
+ "resendVerification": "Reenviar correo de confirmación",
+ "verifyResent": "Correo de confirmación enviado. Revisa tu bandeja de entrada.",
+ "verifyResendFailed": "No se pudo enviar el correo de confirmación. Inténtalo más tarde.",
+ "verifyMissingEmail": "Introduce tu dirección de correo.",
+ "verifyLoading": "Confirmando tu correo…",
+ "verifySuccessTitle": "Correo confirmado",
+ "verifySuccessDescription": "Tu cuenta está lista. Ya puedes iniciar sesión.",
+ "verifyExpiredTitle": "Enlace caducado",
+ "verifyExpiredDescription": "Este enlace de confirmación ha caducado. Solicita uno nuevo.",
+ "verifyInvalidTitle": "Enlace no válido",
+ "verifyInvalidDescription": "Este enlace de confirmación no es válido o ya se usó.",
+ "emailNotVerified": "Confirma tu correo antes de iniciar sesión.",
+ "emailVerifiedBanner": "Correo confirmado. Ya puedes iniciar sesión.",
+ "invalidCredentials": "Correo o contraseña incorrectos."
},
"sidebar": {
"notes": "Notas",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Paquete intensivo",
"buyPack": "Comprar",
"packCheckoutSuccess": "¡Paquete de créditos añadido a su saldo!",
- "packCheckoutFailed": "No se pudo iniciar la compra. Compruebe la config de Stripe o inténtelo de nuevo."
+ "packCheckoutFailed": "No se pudo iniciar la compra. Compruebe la config de Stripe o inténtelo de nuevo.",
+ "startTrialCta": "Probar {days} días gratis",
+ "trialFeature": "Prueba gratis de {days} días (tarjeta requerida)",
+ "trialEndsOn": "Tu prueba gratuita termina el {date}. Después se te cobrará automáticamente.",
+ "trialEndsLabel": "Fin de la prueba"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Soporte dedicado",
"feature5": "Onboarding en vivo"
},
- "basicPrice": "Gratis"
+ "basicPrice": "Gratis",
+ "savePercent": "Ahorra ~17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "A medida",
+ "trialBadge": "Prueba gratis {days} días",
+ "trialFeature": "Prueba gratis de {days} días (tarjeta requerida)",
+ "trialCta": "Probar {days} días gratis"
},
"cta": {
"title": "Deja de perder tus mejores ideas.",
diff --git a/memento-note/locales/fa.json b/memento-note/locales/fa.json
index 60e087d..8d04415 100644
--- a/memento-note/locales/fa.json
+++ b/memento-note/locales/fa.json
@@ -38,7 +38,24 @@
"privacyTerms": "© ۲۰۲۵ Memento Labs — حریم خصوصی · شرایط",
"sessionExpired": "سایت شما با ناوبری و فهرست مطالب تولید میشود",
"welcomeBack": "خوش آمدید",
- "welcomeBackSubtitle": "اعتبارنامههای خود را برای دسترسی به یادداشتهایتان وارد کنید."
+ "welcomeBackSubtitle": "اعتبارنامههای خود را برای دسترسی به یادداشتهایتان وارد کنید.",
+ "checkEmailTitle": "ایمیل خود را بررسی کنید",
+ "checkEmailDescription": "لینک تأیید را به {email} فرستادیم. قبل از ورود آن را باز کنید تا حساب فعال شود.",
+ "checkEmailDescriptionGeneric": "لینک تأیید را به ایمیل شما فرستادیم. قبل از ورود آن را باز کنید تا حساب فعال شود.",
+ "resendVerification": "ارسال دوباره ایمیل تأیید",
+ "verifyResent": "ایمیل تأیید ارسال شد. صندوق ورودی را بررسی کنید.",
+ "verifyResendFailed": "ارسال ایمیل تأیید ممکن نشد. بعداً دوباره تلاش کنید.",
+ "verifyMissingEmail": "آدرس ایمیل خود را وارد کنید.",
+ "verifyLoading": "در حال تأیید ایمیل…",
+ "verifySuccessTitle": "ایمیل تأیید شد",
+ "verifySuccessDescription": "حساب شما آماده است. اکنون میتوانید وارد شوید.",
+ "verifyExpiredTitle": "لینک منقضی شده",
+ "verifyExpiredDescription": "این لینک تأیید منقضی شده است. یک لینک جدید درخواست کنید.",
+ "verifyInvalidTitle": "لینک نامعتبر",
+ "verifyInvalidDescription": "این لینک تأیید نامعتبر است یا قبلاً استفاده شده.",
+ "emailNotVerified": "قبل از ورود ایمیل خود را تأیید کنید.",
+ "emailVerifiedBanner": "ایمیل تأیید شد. اکنون میتوانید وارد شوید.",
+ "invalidCredentials": "ایمیل یا رمز عبور نادرست است."
},
"sidebar": {
"notes": "یادداشتها",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "بسته قدرتمند",
"buyPack": "خرید",
"packCheckoutSuccess": "بسته اعتبار به موجودی شما اضافه شد!",
- "packCheckoutFailed": "شروع خرید ممکن نشد. پیکربندی Stripe را بررسی کنید یا دوباره تلاش کنید."
+ "packCheckoutFailed": "شروع خرید ممکن نشد. پیکربندی Stripe را بررسی کنید یا دوباره تلاش کنید.",
+ "startTrialCta": "شروع آزمایش رایگان {days} روزه",
+ "trialFeature": "آزمایش رایگان {days} روزه (نیاز به کارت)",
+ "trialEndsOn": "آزمایش رایگان شما در {date} تمام میشود. سپس بهطور خودکار صورتحساب صادر میشود.",
+ "trialEndsLabel": "پایان آزمایش"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "پشتیبانی اختصاصی",
"feature5": "آنبوردینگ زنده"
},
- "basicPrice": "رایگان"
+ "basicPrice": "رایگان",
+ "savePercent": "حدود ۱۷٪ صرفهجویی",
+ "proMonthly": "۹٫۹۰€",
+ "proAnnualMonthly": "۸٫۲۵€",
+ "businessMonthly": "۲۹٫۹۰€",
+ "businessAnnualMonthly": "۲۴٫۹۲€",
+ "enterprisePrice": "قیمت سفارشی",
+ "trialBadge": "آزمایش رایگان {days} روزه",
+ "trialFeature": "آزمایش رایگان {days} روزه (نیاز به کارت)",
+ "trialCta": "شروع آزمایش رایگان {days} روزه"
},
"cta": {
"title": "از دست دادن بهترین ایدهها را متوقف کنید.",
diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json
index 908fd0d..aca8e7e 100644
--- a/memento-note/locales/fr.json
+++ b/memento-note/locales/fr.json
@@ -323,6 +323,7 @@
"switchType": "Passer en {type}",
"saveNow": "Enregistrer maintenant",
"backToCollection": "Retour à la collection",
+ "backToDashboard": "Retour au dashboard",
"markdownEditingTitle": "Revenir à l'édition",
"markdownPreviewTitle": "Aperçu",
"brainstormThisIdea": "Brainstormer cette idée",
@@ -4294,6 +4295,8 @@
"toReview": "À traiter",
"allCaughtUp": "Tout est à jour.",
"toOrganize": "à organiser",
+ "inboxSeeAll": "Voir les {count}",
+ "inboxEmpty": "Rien à classer.",
"review": "Révisions",
"cardsDue": "cartes dues",
"reminders": "Rappels",
diff --git a/memento-note/locales/hi.json b/memento-note/locales/hi.json
index 58f7293..b3a703f 100644
--- a/memento-note/locales/hi.json
+++ b/memento-note/locales/hi.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — गोपनीयता · शर्तें",
"sessionExpired": "आपकी साइट नेविगेशन और विषय-सूची के साथ बनाई जाती है",
"welcomeBack": "वापसी पर स्वागत है",
- "welcomeBackSubtitle": "अपने नोट्स तक पहुँचने के लिए अपनी प्रमाणीकरण जानकारी दर्ज करें।"
+ "welcomeBackSubtitle": "अपने नोट्स तक पहुँचने के लिए अपनी प्रमाणीकरण जानकारी दर्ज करें।",
+ "checkEmailTitle": "अपना ईमेल देखें",
+ "checkEmailDescription": "हमने {email} पर पुष्टि लिंक भेजा है। साइन इन से पहले खाता सक्रिय करने के लिए इसे खोलें।",
+ "checkEmailDescriptionGeneric": "हमने आपके ईमेल पर पुष्टि लिंक भेजा है। साइन इन से पहले खाता सक्रिय करने के लिए इसे खोलें।",
+ "resendVerification": "पुष्टि ईमेल फिर से भेजें",
+ "verifyResent": "पुष्टि ईमेल भेजा गया। इनबॉक्स देखें।",
+ "verifyResendFailed": "पुष्टि ईमेल नहीं भेजा जा सका। बाद में फिर कोशिश करें।",
+ "verifyMissingEmail": "अपना ईमेल पता दर्ज करें।",
+ "verifyLoading": "ईमेल की पुष्टि हो रही है…",
+ "verifySuccessTitle": "ईमेल पुष्टि हो गई",
+ "verifySuccessDescription": "आपका खाता तैयार है। अब साइन इन कर सकते हैं।",
+ "verifyExpiredTitle": "लिंक समाप्त",
+ "verifyExpiredDescription": "यह पुष्टि लिंक समाप्त हो गया है। नया लिंक माँगें।",
+ "verifyInvalidTitle": "अमान्य लिंक",
+ "verifyInvalidDescription": "यह पुष्टि लिंक अमान्य है या पहले ही उपयोग हो चुका है।",
+ "emailNotVerified": "साइन इन से पहले अपना ईमेल पुष्टि करें।",
+ "emailVerifiedBanner": "ईमेल पुष्टि हो गई। अब साइन इन करें।",
+ "invalidCredentials": "ईमेल या पासवर्ड गलत है।"
},
"sidebar": {
"notes": "नोट्स",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "पावर पैक",
"buyPack": "खरीदें",
"packCheckoutSuccess": "क्रेडिट पैक आपके शेष में जोड़ा गया!",
- "packCheckoutFailed": "खरीद शुरू नहीं हो सकी। Stripe कॉन्फ़िग जांचें या पुनः प्रयास करें।"
+ "packCheckoutFailed": "खरीद शुरू नहीं हो सकी। Stripe कॉन्फ़िग जांचें या पुनः प्रयास करें।",
+ "startTrialCta": "{days} दिन मुफ़्त आज़माएँ",
+ "trialFeature": "{days} दिन का मुफ़्त ट्रायल (कार्ड आवश्यक)",
+ "trialEndsOn": "आपका मुफ़्त ट्रायल {date} को समाप्त होता है। उसके बाद स्वचालित रूप से शुल्क लगेगा।",
+ "trialEndsLabel": "ट्रायल समाप्त"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "समर्पित सपोर्ट",
"feature5": "लाइव ऑनबोर्डिंग"
},
- "basicPrice": "मुफ़्त"
+ "basicPrice": "मुफ़्त",
+ "savePercent": "~17% बचाएँ",
+ "proMonthly": "€9.90",
+ "proAnnualMonthly": "€8.25",
+ "businessMonthly": "€29.90",
+ "businessAnnualMonthly": "€24.92",
+ "enterprisePrice": "कस्टम",
+ "trialBadge": "{days} दिन का मुफ़्त ट्रायल",
+ "trialFeature": "{days} दिन का मुफ़्त ट्रायल (कार्ड आवश्यक)",
+ "trialCta": "{days} दिन मुफ़्त आज़माएँ"
},
"cta": {
"title": "अपने सबसे अच्छे विचारों को खोना बंद करें।",
diff --git a/memento-note/locales/it.json b/memento-note/locales/it.json
index 9b07ede..a56b981 100644
--- a/memento-note/locales/it.json
+++ b/memento-note/locales/it.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Privacy · Termini",
"sessionExpired": "Il tuo sito viene generato con navigazione e sommario",
"welcomeBack": "Bentornato",
- "welcomeBackSubtitle": "Inserisci le tue credenziali per accedere alle tue note."
+ "welcomeBackSubtitle": "Inserisci le tue credenziali per accedere alle tue note.",
+ "checkEmailTitle": "Controlla la tua e-mail",
+ "checkEmailDescription": "Abbiamo inviato un link di conferma a {email}. Aprilo per attivare l’account prima di accedere.",
+ "checkEmailDescriptionGeneric": "Abbiamo inviato un link di conferma alla tua e-mail. Aprilo per attivare l’account prima di accedere.",
+ "resendVerification": "Reinvia e-mail di conferma",
+ "verifyResent": "E-mail di conferma inviata. Controlla la posta in arrivo.",
+ "verifyResendFailed": "Impossibile inviare l’e-mail di conferma. Riprova più tardi.",
+ "verifyMissingEmail": "Inserisci il tuo indirizzo e-mail.",
+ "verifyLoading": "Conferma dell’e-mail in corso…",
+ "verifySuccessTitle": "E-mail confermata",
+ "verifySuccessDescription": "Il tuo account è pronto. Ora puoi accedere.",
+ "verifyExpiredTitle": "Link scaduto",
+ "verifyExpiredDescription": "Questo link di conferma è scaduto. Richiedine uno nuovo.",
+ "verifyInvalidTitle": "Link non valido",
+ "verifyInvalidDescription": "Questo link di conferma non è valido o è già stato usato.",
+ "emailNotVerified": "Conferma la tua e-mail prima di accedere.",
+ "emailVerifiedBanner": "E-mail confermata. Ora puoi accedere.",
+ "invalidCredentials": "E-mail o password non validi."
},
"sidebar": {
"notes": "Note",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Pacchetto power",
"buyPack": "Acquista",
"packCheckoutSuccess": "Pacchetto crediti aggiunto al saldo!",
- "packCheckoutFailed": "Acquisto del pacchetto non riuscito. Verifica la config Stripe o riprova."
+ "packCheckoutFailed": "Acquisto del pacchetto non riuscito. Verifica la config Stripe o riprova.",
+ "startTrialCta": "Prova {days} giorni gratis",
+ "trialFeature": "Prova gratuita di {days} giorni (carta richiesta)",
+ "trialEndsOn": "La prova gratuita termina il {date}. Poi verrai addebitato automaticamente.",
+ "trialEndsLabel": "Fine prova"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Supporto dedicato",
"feature5": "Onboarding live"
},
- "basicPrice": "Gratis"
+ "basicPrice": "Gratis",
+ "savePercent": "Risparmia ~17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "Su preventivo",
+ "trialBadge": "Prova gratuita {days} giorni",
+ "trialFeature": "Prova gratuita di {days} giorni (carta richiesta)",
+ "trialCta": "Prova {days} giorni gratis"
},
"cta": {
"title": "Smetti di perdere le tue idee migliori.",
diff --git a/memento-note/locales/ja.json b/memento-note/locales/ja.json
index 48662e6..56fb77b 100644
--- a/memento-note/locales/ja.json
+++ b/memento-note/locales/ja.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — プライバシー · 利用規約",
"sessionExpired": "サイトはナビゲーションと目次付きで生成されます",
"welcomeBack": "おかえりなさい",
- "welcomeBackSubtitle": "ノートにアクセスするには認証情報を入力してください。"
+ "welcomeBackSubtitle": "ノートにアクセスするには認証情報を入力してください。",
+ "checkEmailTitle": "メールを確認してください",
+ "checkEmailDescription": "{email} に確認リンクを送信しました。ログイン前に開いてアカウントを有効化してください。",
+ "checkEmailDescriptionGeneric": "確認リンクをメールで送信しました。ログイン前に開いてアカウントを有効化してください。",
+ "resendVerification": "確認メールを再送信",
+ "verifyResent": "確認メールを送信しました。受信箱を確認してください。",
+ "verifyResendFailed": "確認メールを送信できませんでした。後でもう一度お試しください。",
+ "verifyMissingEmail": "メールアドレスを入力してください。",
+ "verifyLoading": "メールを確認しています…",
+ "verifySuccessTitle": "メール確認完了",
+ "verifySuccessDescription": "アカウントの準備ができました。ログインできます。",
+ "verifyExpiredTitle": "リンクの期限切れ",
+ "verifyExpiredDescription": "この確認リンクは期限切れです。新しいリンクをリクエストしてください。",
+ "verifyInvalidTitle": "無効なリンク",
+ "verifyInvalidDescription": "この確認リンクは無効か、すでに使用されています。",
+ "emailNotVerified": "ログイン前にメールを確認してください。",
+ "emailVerifiedBanner": "メール確認済みです。ログインできます。",
+ "invalidCredentials": "メールまたはパスワードが正しくありません。"
},
"sidebar": {
"notes": "ノート",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "パワーパック",
"buyPack": "購入",
"packCheckoutSuccess": "クレジットパックが残高に追加されました!",
- "packCheckoutFailed": "購入を開始できませんでした。Stripe設定を確認するか再試行してください。"
+ "packCheckoutFailed": "購入を開始できませんでした。Stripe設定を確認するか再試行してください。",
+ "startTrialCta": "{days}日間無料で試す",
+ "trialFeature": "{days}日間無料トライアル(カード登録が必要)",
+ "trialEndsOn": "無料トライアルは {date} に終了します。その後、自動的に請求されます。",
+ "trialEndsLabel": "トライアル終了"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "専任サポート",
"feature5": "ライブオンボーディング"
},
- "basicPrice": "無料"
+ "basicPrice": "無料",
+ "savePercent": "約17%お得",
+ "proMonthly": "€9.90",
+ "proAnnualMonthly": "€8.25",
+ "businessMonthly": "€29.90",
+ "businessAnnualMonthly": "€24.92",
+ "enterprisePrice": "お問い合わせ",
+ "trialBadge": "{days}日間無料トライアル",
+ "trialFeature": "{days}日間無料トライアル(カード登録が必要)",
+ "trialCta": "{days}日間無料で試す"
},
"cta": {
"title": "最高のアイデアを失うのをやめる。",
diff --git a/memento-note/locales/ko.json b/memento-note/locales/ko.json
index a30f4d1..26fb58a 100644
--- a/memento-note/locales/ko.json
+++ b/memento-note/locales/ko.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — 개인정보 · 약관",
"sessionExpired": "사이트가 탐색 및 목차와 함께 생성됩니다",
"welcomeBack": "다시 오신 것을 환영합니다",
- "welcomeBackSubtitle": "노트에 액세스하려면 자격 증명을 입력하세요."
+ "welcomeBackSubtitle": "노트에 액세스하려면 자격 증명을 입력하세요.",
+ "checkEmailTitle": "이메일을 확인하세요",
+ "checkEmailDescription": "{email}(으)로 확인 링크를 보냈습니다. 로그인하기 전에 열어 계정을 활성화하세요.",
+ "checkEmailDescriptionGeneric": "확인 링크를 이메일로 보냈습니다. 로그인하기 전에 열어 계정을 활성화하세요.",
+ "resendVerification": "확인 이메일 다시 보내기",
+ "verifyResent": "확인 이메일을 보냈습니다. 받은편지함을 확인하세요.",
+ "verifyResendFailed": "확인 이메일을 보낼 수 없습니다. 나중에 다시 시도하세요.",
+ "verifyMissingEmail": "이메일 주소를 입력하세요.",
+ "verifyLoading": "이메일을 확인하는 중…",
+ "verifySuccessTitle": "이메일 확인 완료",
+ "verifySuccessDescription": "계정이 준비되었습니다. 이제 로그인할 수 있습니다.",
+ "verifyExpiredTitle": "링크 만료",
+ "verifyExpiredDescription": "이 확인 링크는 만료되었습니다. 새 링크를 요청하세요.",
+ "verifyInvalidTitle": "유효하지 않은 링크",
+ "verifyInvalidDescription": "이 확인 링크는 유효하지 않거나 이미 사용되었습니다.",
+ "emailNotVerified": "로그인하기 전에 이메일을 확인하세요.",
+ "emailVerifiedBanner": "이메일이 확인되었습니다. 이제 로그인할 수 있습니다.",
+ "invalidCredentials": "이메일 또는 비밀번호가 올바르지 않습니다."
},
"sidebar": {
"notes": "노트",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "파워 팩",
"buyPack": "구매",
"packCheckoutSuccess": "크레딧 팩이 잔액에 추가되었습니다!",
- "packCheckoutFailed": "구매를 시작할 수 없습니다. Stripe 설정을 확인하거나 다시 시도하세요."
+ "packCheckoutFailed": "구매를 시작할 수 없습니다. Stripe 설정을 확인하거나 다시 시도하세요.",
+ "startTrialCta": "{days}일 무료로 시작",
+ "trialFeature": "{days}일 무료 체험 (카드 등록 필요)",
+ "trialEndsOn": "무료 체험이 {date}에 종료됩니다. 이후 자동으로 결제됩니다.",
+ "trialEndsLabel": "체험 종료"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "전담 지원",
"feature5": "라이브 온보딩"
},
- "basicPrice": "무료"
+ "basicPrice": "무료",
+ "savePercent": "약 17% 절약",
+ "proMonthly": "€9.90",
+ "proAnnualMonthly": "€8.25",
+ "businessMonthly": "€29.90",
+ "businessAnnualMonthly": "€24.92",
+ "enterprisePrice": "맞춤 견적",
+ "trialBadge": "{days}일 무료 체험",
+ "trialFeature": "{days}일 무료 체험 (카드 등록 필요)",
+ "trialCta": "{days}일 무료로 시작"
},
"cta": {
"title": "최고의 아이디어를 잃는 일을 멈추세요.",
diff --git a/memento-note/locales/nl.json b/memento-note/locales/nl.json
index 9ad51f6..a553b25 100644
--- a/memento-note/locales/nl.json
+++ b/memento-note/locales/nl.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Privacy · Voorwaarden",
"sessionExpired": "Uw site wordt gegenereerd met navigatie en inhoudsopgave",
"welcomeBack": "Welkom terug",
- "welcomeBackSubtitle": "Voer uw inloggegevens in om toegang te krijgen tot uw notities."
+ "welcomeBackSubtitle": "Voer uw inloggegevens in om toegang te krijgen tot uw notities.",
+ "checkEmailTitle": "Controleer je e-mail",
+ "checkEmailDescription": "We hebben een bevestigingslink naar {email} gestuurd. Open die om je account te activeren voordat je inlogt.",
+ "checkEmailDescriptionGeneric": "We hebben een bevestigingslink naar je e-mail gestuurd. Open die om je account te activeren voordat je inlogt.",
+ "resendVerification": "Bevestigingsmail opnieuw versturen",
+ "verifyResent": "Bevestigingsmail verzonden. Controleer je inbox.",
+ "verifyResendFailed": "Bevestigingsmail kon niet worden verzonden. Probeer later opnieuw.",
+ "verifyMissingEmail": "Voer je e-mailadres in.",
+ "verifyLoading": "E-mail wordt bevestigd…",
+ "verifySuccessTitle": "E-mail bevestigd",
+ "verifySuccessDescription": "Je account is klaar. Je kunt nu inloggen.",
+ "verifyExpiredTitle": "Link verlopen",
+ "verifyExpiredDescription": "Deze bevestigingslink is verlopen. Vraag een nieuwe aan.",
+ "verifyInvalidTitle": "Ongeldige link",
+ "verifyInvalidDescription": "Deze bevestigingslink is ongeldig of al gebruikt.",
+ "emailNotVerified": "Bevestig je e-mail voordat je inlogt.",
+ "emailVerifiedBanner": "E-mail bevestigd. Je kunt nu inloggen.",
+ "invalidCredentials": "Ongeldig e-mailadres of wachtwoord."
},
"sidebar": {
"notes": "Notities",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Powerpakket",
"buyPack": "Kopen",
"packCheckoutSuccess": "Creditpakket toegevoegd aan je saldo!",
- "packCheckoutFailed": "Aankoop mislukt. Controleer de Stripe-config of probeer opnieuw."
+ "packCheckoutFailed": "Aankoop mislukt. Controleer de Stripe-config of probeer opnieuw.",
+ "startTrialCta": "{days} dagen gratis starten",
+ "trialFeature": "{days} dagen gratis proberen (kaart vereist)",
+ "trialEndsOn": "Je gratis proefperiode eindigt op {date}. Daarna word je automatisch gefactureerd.",
+ "trialEndsLabel": "Einde proefperiode"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Dedicated support",
"feature5": "Live onboarding"
},
- "basicPrice": "Gratis"
+ "basicPrice": "Gratis",
+ "savePercent": "Bespaar ~17%",
+ "proMonthly": "€9,90",
+ "proAnnualMonthly": "€8,25",
+ "businessMonthly": "€29,90",
+ "businessAnnualMonthly": "€24,92",
+ "enterprisePrice": "Op maat",
+ "trialBadge": "{days} dagen gratis proberen",
+ "trialFeature": "{days} dagen gratis proberen (kaart vereist)",
+ "trialCta": "{days} dagen gratis starten"
},
"cta": {
"title": "Stop met het verliezen van je beste ideeën.",
diff --git a/memento-note/locales/pl.json b/memento-note/locales/pl.json
index a9ce397..464803a 100644
--- a/memento-note/locales/pl.json
+++ b/memento-note/locales/pl.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Prywatność · Warunki",
"sessionExpired": "Twoja strona jest generowana z nawigacją i spisem treści",
"welcomeBack": "Witamy ponownie",
- "welcomeBackSubtitle": "Wprowadź swoje dane logowania, aby uzyskać dostęp do notatek."
+ "welcomeBackSubtitle": "Wprowadź swoje dane logowania, aby uzyskać dostęp do notatek.",
+ "checkEmailTitle": "Sprawdź e-mail",
+ "checkEmailDescription": "Wysłaliśmy link potwierdzający na {email}. Otwórz go, aby aktywować konto przed logowaniem.",
+ "checkEmailDescriptionGeneric": "Wysłaliśmy link potwierdzający na Twój e-mail. Otwórz go, aby aktywować konto przed logowaniem.",
+ "resendVerification": "Wyślij ponownie e-mail potwierdzający",
+ "verifyResent": "Wysłano e-mail potwierdzający. Sprawdź skrzynkę.",
+ "verifyResendFailed": "Nie udało się wysłać e-maila potwierdzającego. Spróbuj później.",
+ "verifyMissingEmail": "Podaj adres e-mail.",
+ "verifyLoading": "Potwierdzanie e-maila…",
+ "verifySuccessTitle": "E-mail potwierdzony",
+ "verifySuccessDescription": "Konto jest gotowe. Możesz się zalogować.",
+ "verifyExpiredTitle": "Link wygasł",
+ "verifyExpiredDescription": "Ten link potwierdzający wygasł. Poproś o nowy.",
+ "verifyInvalidTitle": "Nieprawidłowy link",
+ "verifyInvalidDescription": "Ten link potwierdzający jest nieprawidłowy lub został już użyty.",
+ "emailNotVerified": "Potwierdź e-mail przed zalogowaniem.",
+ "emailVerifiedBanner": "E-mail potwierdzony. Możesz się zalogować.",
+ "invalidCredentials": "Nieprawidłowy e-mail lub hasło."
},
"sidebar": {
"notes": "Notatki",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Pakiet power",
"buyPack": "Kup",
"packCheckoutSuccess": "Pakiet kredytów dodany do salda!",
- "packCheckoutFailed": "Nie udało się rozpocząć zakupu. Sprawdź konfigurację Stripe lub spróbuj ponownie."
+ "packCheckoutFailed": "Nie udało się rozpocząć zakupu. Sprawdź konfigurację Stripe lub spróbuj ponownie.",
+ "startTrialCta": "Wypróbuj {days} dni za darmo",
+ "trialFeature": "{days}-dniowy okres próbny (wymagana karta)",
+ "trialEndsOn": "Twój okres próbny kończy się {date}. Potem nastąpi automatyczne obciążenie.",
+ "trialEndsLabel": "Koniec okresu próbnego"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Dedykowane wsparcie",
"feature5": "Onboarding na żywo"
},
- "basicPrice": "Za darmo"
+ "basicPrice": "Za darmo",
+ "savePercent": "Oszczędź ~17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "Indywidualnie",
+ "trialBadge": "{days} dni za darmo",
+ "trialFeature": "{days}-dniowy okres próbny (wymagana karta)",
+ "trialCta": "Wypróbuj {days} dni za darmo"
},
"cta": {
"title": "Przestań tracić najlepsze pomysły.",
diff --git a/memento-note/locales/pt.json b/memento-note/locales/pt.json
index b1cedde..c655b07 100644
--- a/memento-note/locales/pt.json
+++ b/memento-note/locales/pt.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Privacidade · Termos",
"sessionExpired": "Seu site é gerado com navegação e sumário",
"welcomeBack": "Bem-vindo de volta",
- "welcomeBackSubtitle": "Digite suas credenciais para acessar suas notas."
+ "welcomeBackSubtitle": "Digite suas credenciais para acessar suas notas.",
+ "checkEmailTitle": "Verifique o seu e-mail",
+ "checkEmailDescription": "Enviámos um link de confirmação para {email}. Abra-o para ativar a conta antes de entrar.",
+ "checkEmailDescriptionGeneric": "Enviámos um link de confirmação para o seu e-mail. Abra-o para ativar a conta antes de entrar.",
+ "resendVerification": "Reenviar e-mail de confirmação",
+ "verifyResent": "E-mail de confirmação enviado. Verifique a caixa de entrada.",
+ "verifyResendFailed": "Não foi possível enviar o e-mail de confirmação. Tente mais tarde.",
+ "verifyMissingEmail": "Introduza o seu endereço de e-mail.",
+ "verifyLoading": "A confirmar o seu e-mail…",
+ "verifySuccessTitle": "E-mail confirmado",
+ "verifySuccessDescription": "A sua conta está pronta. Já pode iniciar sessão.",
+ "verifyExpiredTitle": "Link expirado",
+ "verifyExpiredDescription": "Este link de confirmação expirou. Peça um novo.",
+ "verifyInvalidTitle": "Link inválido",
+ "verifyInvalidDescription": "Este link de confirmação é inválido ou já foi usado.",
+ "emailNotVerified": "Confirme o seu e-mail antes de iniciar sessão.",
+ "emailVerifiedBanner": "E-mail confirmado. Já pode iniciar sessão.",
+ "invalidCredentials": "E-mail ou palavra-passe incorretos."
},
"sidebar": {
"notes": "Notas",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Pacote intensivo",
"buyPack": "Comprar",
"packCheckoutSuccess": "Pacote de créditos adicionado ao saldo!",
- "packCheckoutFailed": "Falha ao iniciar a compra. Verifique a config Stripe ou tente de novo."
+ "packCheckoutFailed": "Falha ao iniciar a compra. Verifique a config Stripe ou tente de novo.",
+ "startTrialCta": "Experimentar {days} dias grátis",
+ "trialFeature": "Teste grátis de {days} dias (cartão necessário)",
+ "trialEndsOn": "O seu teste gratuito termina em {date}. Depois será cobrado automaticamente.",
+ "trialEndsLabel": "Fim do teste"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Suporte dedicado",
"feature5": "Onboarding ao vivo"
},
- "basicPrice": "Grátis"
+ "basicPrice": "Grátis",
+ "savePercent": "Economize ~17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "Sob consulta",
+ "trialBadge": "Teste grátis {days} dias",
+ "trialFeature": "Teste grátis de {days} dias (cartão necessário)",
+ "trialCta": "Experimentar {days} dias grátis"
},
"cta": {
"title": "Pare de perder suas melhores ideias.",
diff --git a/memento-note/locales/ru.json b/memento-note/locales/ru.json
index e6af25b..5327155 100644
--- a/memento-note/locales/ru.json
+++ b/memento-note/locales/ru.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — Конфиденциальность · Условия",
"sessionExpired": "Ваш сайт создаётся с навигацией и оглавлением",
"welcomeBack": "С возвращением",
- "welcomeBackSubtitle": "Введите свои учётные данные для доступа к заметкам."
+ "welcomeBackSubtitle": "Введите свои учётные данные для доступа к заметкам.",
+ "checkEmailTitle": "Проверьте почту",
+ "checkEmailDescription": "Мы отправили ссылку подтверждения на {email}. Откройте её, чтобы активировать аккаунт перед входом.",
+ "checkEmailDescriptionGeneric": "Мы отправили ссылку подтверждения на вашу почту. Откройте её, чтобы активировать аккаунт перед входом.",
+ "resendVerification": "Отправить письмо ещё раз",
+ "verifyResent": "Письмо подтверждения отправлено. Проверьте входящие.",
+ "verifyResendFailed": "Не удалось отправить письмо подтверждения. Попробуйте позже.",
+ "verifyMissingEmail": "Введите адрес электронной почты.",
+ "verifyLoading": "Подтверждение почты…",
+ "verifySuccessTitle": "Почта подтверждена",
+ "verifySuccessDescription": "Аккаунт готов. Теперь можно войти.",
+ "verifyExpiredTitle": "Ссылка устарела",
+ "verifyExpiredDescription": "Срок действия ссылки истёк. Запросите новую.",
+ "verifyInvalidTitle": "Недействительная ссылка",
+ "verifyInvalidDescription": "Эта ссылка недействительна или уже использована.",
+ "emailNotVerified": "Подтвердите почту перед входом.",
+ "emailVerifiedBanner": "Почта подтверждена. Можно войти.",
+ "invalidCredentials": "Неверный e-mail или пароль."
},
"sidebar": {
"notes": "Заметки",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "Мощный пакет",
"buyPack": "Купить",
"packCheckoutSuccess": "Пакет кредитов добавлен на баланс!",
- "packCheckoutFailed": "Не удалось начать покупку. Проверьте настройки Stripe или повторите попытку."
+ "packCheckoutFailed": "Не удалось начать покупку. Проверьте настройки Stripe или повторите попытку.",
+ "startTrialCta": "Попробовать {days} дней бесплатно",
+ "trialFeature": "Бесплатный период {days} дней (нужна карта)",
+ "trialEndsOn": "Ваш пробный период заканчивается {date}. Затем списание произойдёт автоматически.",
+ "trialEndsLabel": "Конец пробного периода"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "Выделенная поддержка",
"feature5": "Live-онбординг"
},
- "basicPrice": "Бесплатно"
+ "basicPrice": "Бесплатно",
+ "savePercent": "Экономия ~17%",
+ "proMonthly": "9,90€",
+ "proAnnualMonthly": "8,25€",
+ "businessMonthly": "29,90€",
+ "businessAnnualMonthly": "24,92€",
+ "enterprisePrice": "По запросу",
+ "trialBadge": "{days} дней бесплатно",
+ "trialFeature": "Бесплатный период {days} дней (нужна карта)",
+ "trialCta": "Попробовать {days} дней бесплатно"
},
"cta": {
"title": "Хватит терять лучшие идеи.",
diff --git a/memento-note/locales/zh.json b/memento-note/locales/zh.json
index d9405d3..e4b222f 100644
--- a/memento-note/locales/zh.json
+++ b/memento-note/locales/zh.json
@@ -38,7 +38,24 @@
"privacyTerms": "© 2025 Memento Labs — 隐私 · 条款",
"sessionExpired": "您的网站将包含导航和目录地生成",
"welcomeBack": "欢迎回来",
- "welcomeBackSubtitle": "输入你的凭据以访问笔记。"
+ "welcomeBackSubtitle": "输入你的凭据以访问笔记。",
+ "checkEmailTitle": "请查收邮件",
+ "checkEmailDescription": "我们已向 {email} 发送确认链接。请打开链接以激活账户后再登录。",
+ "checkEmailDescriptionGeneric": "我们已向您的邮箱发送确认链接。请打开链接以激活账户后再登录。",
+ "resendVerification": "重新发送确认邮件",
+ "verifyResent": "确认邮件已发送,请检查收件箱。",
+ "verifyResendFailed": "无法发送确认邮件,请稍后再试。",
+ "verifyMissingEmail": "请输入电子邮箱地址。",
+ "verifyLoading": "正在确认邮箱…",
+ "verifySuccessTitle": "邮箱已确认",
+ "verifySuccessDescription": "账户已就绪,现在可以登录。",
+ "verifyExpiredTitle": "链接已过期",
+ "verifyExpiredDescription": "此确认链接已过期,请重新申请。",
+ "verifyInvalidTitle": "无效链接",
+ "verifyInvalidDescription": "此确认链接无效或已被使用。",
+ "emailNotVerified": "登录前请先确认邮箱。",
+ "emailVerifiedBanner": "邮箱已确认,现在可以登录。",
+ "invalidCredentials": "邮箱或密码不正确。"
},
"sidebar": {
"notes": "笔记",
@@ -1580,7 +1597,58 @@
"STRIPE_PRICE_CREDITS_S": "Pack S — 100 credits (price ID)",
"STRIPE_PRICE_CREDITS_M": "Pack M — 500 credits (price ID)",
"STRIPE_PRICE_CREDITS_L": "Pack L — 2,000 credits (price ID)",
- "packsCatalogTitle": "Pack catalogue (code)"
+ "packsCatalogTitle": "Pack catalogue (code)",
+ "healthTitle": "Stripe health check",
+ "healthDescription": "Runtime status of keys, webhooks, price IDs and billing flag (secrets are never shown).",
+ "healthSecret": "Secret key (server)",
+ "healthPublishable": "Publishable key",
+ "healthWebhook": "Webhook secret",
+ "healthBillingFlag": "Billing enabled",
+ "healthTrial": "Free trial",
+ "trialDaysValue": "{days} days on first checkout",
+ "modeTest": "Test mode (sk_test_…)",
+ "modeLive": "Live mode (sk_live_…)",
+ "modePlaceholder": "Placeholder / invalid key",
+ "modeMissing": "Not configured",
+ "configured": "Configured",
+ "missing": "Missing",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "priceStatusTitle": "Price IDs vs Stripe",
+ "colKey": "Plan",
+ "colPriceId": "Price ID",
+ "colSource": "Source",
+ "colStripe": "Stripe amount",
+ "priceError": "Lookup failed",
+ "inactive": "inactive",
+ "notChecked": "Not checked (no Stripe key)",
+ "subsTitle": "Subscriptions overview",
+ "subsDescription": "Counts from the local database (synced via Stripe webhooks).",
+ "statPaid": "Active + trial",
+ "statTrialing": "On trial",
+ "statPastDue": "Past due",
+ "statCanceling": "Cancel at period end",
+ "byTier": "By tier",
+ "byStatus": "By status",
+ "usersWithoutSub": "Users with no Subscription row",
+ "noSubs": "No subscriptions yet",
+ "recentSubs": "Recent paid / trial accounts",
+ "colUser": "User",
+ "colTier": "Tier",
+ "colStatus": "Status",
+ "colPeriod": "Period / trial end",
+ "canceling": "canceling",
+ "manualTier": "manual (no Stripe sub)",
+ "trialUntil": "Trial until {date}",
+ "testGuideTitle": "How to test Stripe locally",
+ "testGuideDescription": "Checklist to validate checkout, webhooks and trial.",
+ "testStep1": "Stripe Dashboard → Test mode ON. Create Pro/Business products + monthly/annual prices + credit packs.",
+ "testStep2": "Put sk_test_…, pk_test_… in .env. Put price_… IDs in Admin → Billing (or env) and enable billing.",
+ "testStep3": "Copy the whsec_… into STRIPE_WEBHOOK_SECRET and restart the app.",
+ "testStep4": "npm run dev → open /settings/billing as a BASIC user.",
+ "testStep5": "Start Pro checkout. Card: 4242 4242 4242 4242, any future expiry, any CVC. Expect a 7-day trial.",
+ "testStep6": "Confirm Admin → Billing shows TRIALING, and /settings/billing shows the trial end date.",
+ "testCardHint": "Other cards: 4000000000009995 = payment fails · 4000002500003155 = 3D Secure. Never use real cards in test mode."
}
},
"about": {
@@ -3113,7 +3181,11 @@
"packLName": "加强包",
"buyPack": "购买",
"packCheckoutSuccess": "积分包已加入余额!",
- "packCheckoutFailed": "无法开始购买。请检查 Stripe 配置或重试。"
+ "packCheckoutFailed": "无法开始购买。请检查 Stripe 配置或重试。",
+ "startTrialCta": "免费试用 {days} 天",
+ "trialFeature": "{days} 天免费试用(需绑定支付方式)",
+ "trialEndsOn": "您的免费试用将于 {date} 结束,之后将自动扣费。",
+ "trialEndsLabel": "试用结束"
},
"landing": {
"nav": {
@@ -3295,7 +3367,16 @@
"feature4": "专属支持",
"feature5": "现场入职"
},
- "basicPrice": "免费"
+ "basicPrice": "免费",
+ "savePercent": "节省约 17%",
+ "proMonthly": "€9.90",
+ "proAnnualMonthly": "€8.25",
+ "businessMonthly": "€29.90",
+ "businessAnnualMonthly": "€24.92",
+ "enterprisePrice": "定制方案",
+ "trialBadge": "{days} 天免费试用",
+ "trialFeature": "{days} 天免费试用(需绑定支付方式)",
+ "trialCta": "免费试用 {days} 天"
},
"cta": {
"title": "别再丢掉最好的想法。",
diff --git a/memento-note/prisma/migrations/20260719120000_backfill_email_verified_password_users/migration.sql b/memento-note/prisma/migrations/20260719120000_backfill_email_verified_password_users/migration.sql
new file mode 100644
index 0000000..301f9af
--- /dev/null
+++ b/memento-note/prisma/migrations/20260719120000_backfill_email_verified_password_users/migration.sql
@@ -0,0 +1,6 @@
+-- Grandfather existing password accounts so email-verification only applies to new signups.
+-- Non-destructive: only fills NULL emailVerified for users that already have a password.
+UPDATE "User"
+SET "emailVerified" = COALESCE("emailVerified", "createdAt")
+WHERE "password" IS NOT NULL
+ AND "emailVerified" IS NULL;