feat(native): a plan picker for TREK Cloud, with live prices

The Get TREK Cloud path now leads to a plan picker that reads the live plans
from the site (/api/plans), so the prices and quotas always match the website.
Monthly and yearly in a segmented toggle, the year marked with its saving, each
plan a card with its price and what it includes. Subscribe is wired to finish on
the web order for now; it becomes an Apple/Google in-app purchase once the store
products exist. New strings across all locales (en and de translated).
This commit is contained in:
Maurice
2026-11-05 13:34:47 +01:00
parent 9790ad28e1
commit 5a62aa4541
29 changed files with 667 additions and 2 deletions
+127 -2
View File
@@ -246,8 +246,11 @@ function feature(path: string, text: string): HTMLElement {
}
function showCloud(opts: { back?: boolean } = {}): void {
const cta = el('a', { className: 'btn', href: CLOUD_URL, target: '_blank', rel: 'noopener noreferrer' }, t('native.cloud.cta'), icon(I.chevron, 20, 'chev'))
cta.addEventListener('click', () => void tap())
const cta = el('button', { type: 'button', className: 'btn' }, t('native.cloud.cta'), icon(I.chevron, 20, 'chev'))
cta.addEventListener('click', () => {
void tap()
showPlans()
})
const have = el('button', { type: 'button', className: 'linkbtn' }, t('native.cloud.haveServer'))
have.addEventListener('click', () => {
@@ -271,6 +274,128 @@ function showCloud(opts: { back?: boolean } = {}): void {
)
}
// ── Choose a plan (TREK Cloud) ──────────────────────────────────────────────
interface CloudPlan {
id: string
name: string
monthlyCents: number
yearlyCents: number
aiDocuments: number
storageGb: number
}
function money(cents: number): string {
return new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(cents / 100)
}
async function fetchPlans(): Promise<CloudPlan[] | null> {
try {
const res = await get(`${CLOUD_URL}/api/plans`)
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
const plans = (data as { plans?: CloudPlan[] })?.plans
return Array.isArray(plans) && plans.length ? plans : null
} catch {
return null
}
}
function subscribe(plan: CloudPlan): void {
void tap(ImpactStyle.Medium)
// Phase 2: the subscription is an Apple/Google in-app purchase. Until the
// store products exist, the order is finished on the web for this plan.
window.open(`${CLOUD_URL}/order?plan=${encodeURIComponent(plan.id)}`, '_blank', 'noopener')
}
function planCard(plan: CloudPlan, interval: 'month' | 'year', featured: boolean): HTMLElement {
const price = interval === 'year' ? plan.yearlyCents : plan.monthlyCents
const per = interval === 'year' ? t('native.plans.perYear') : t('native.plans.perMonth')
const feat = (text: string) => el('span', {}, icon(I.check, 16), text)
const sub = el('button', { type: 'button', className: 'btn' }, t('native.plans.subscribe'))
sub.addEventListener('click', () => subscribe(plan))
return el(
'div',
{ className: featured ? 'plan featured' : 'plan' },
el(
'div',
{ className: 'plan-top' },
el('span', { className: 'plan-name' }, plan.name),
el('span', { className: 'plan-price' }, money(price), el('small', {}, per)),
),
el(
'div',
{ className: 'plan-feats' },
feat(t('native.plans.unlimited')),
feat(t('native.plans.storage', { n: String(plan.storageGb) })),
feat(plan.aiDocuments > 0 ? t('native.plans.aiDocs', { n: String(plan.aiDocuments) }) : t('native.plans.noAi')),
),
sub,
)
}
function showPlans(opts: { back?: boolean } = {}): void {
let interval: 'month' | 'year' = 'month'
let plans: CloudPlan[] | null = null
const content = el('div', { className: 'loading' }, el('span', { className: 'spinner' }))
const monthBtn = el('button', { type: 'button' }, t('native.plans.monthly'))
const yearBtn = el('button', { type: 'button' }, t('native.plans.yearly'), el('span', { className: 'save' }, t('native.plans.save')))
const seg = el('div', { className: 'segmented' }, monthBtn, yearBtn)
const paintSeg = () => {
monthBtn.setAttribute('aria-pressed', String(interval === 'month'))
yearBtn.setAttribute('aria-pressed', String(interval === 'year'))
}
const renderList = () => {
const list = plans
if (!list) return
content.className = 'plans'
content.replaceChildren(...list.map((p, i) => planCard(p, interval, i === list.length - 1)))
}
const choose = (next: 'month' | 'year') => {
if (interval === next) return
void tap()
interval = next
paintSeg()
renderList()
}
monthBtn.addEventListener('click', () => choose('month'))
yearBtn.addEventListener('click', () => choose('year'))
paintSeg()
const load = async () => {
plans = await fetchPlans()
if (plans) {
renderList()
return
}
content.className = ''
const retry = el('button', { type: 'button', className: 'btn btn-ghost' }, t('native.plans.retry'))
retry.addEventListener('click', () => {
void tap()
content.className = 'loading'
content.replaceChildren(el('span', { className: 'spinner' }))
void load()
})
content.replaceChildren(el('p', { className: 'error' }, t('native.plans.loadError')), retry)
}
show(
el(
'div',
{},
topbar(() => showCloud({ back: true })),
el('h1', { className: 'title-sm' }, t('native.plans.title')),
el('p', { className: 'sub' }, t('native.plans.sub')),
seg,
content,
el('p', { className: 'note' }, t('native.plans.soon')),
),
opts,
)
void load()
}
// ── Shared actions ──────────────────────────────────────────────────────────
async function runDemo(button: HTMLButtonElement, error?: HTMLElement): Promise<void> {
void tap()
+135
View File
@@ -517,3 +517,138 @@ a.linkbtn {
transition-duration: 0.001ms !important;
}
}
/* ── Plan picker (cloud subscription) ────────────────────────────────────── */
.segmented {
display: flex;
gap: 4px;
padding: 4px;
background: var(--surface);
border: 1px solid var(--edge);
border-radius: 13px;
align-self: stretch;
}
.segmented button {
flex: 1;
font: inherit;
font-weight: 600;
font-size: 0.95rem;
padding: 9px;
border: 0;
border-radius: 9px;
background: none;
color: var(--muted);
cursor: pointer;
transition:
background 0.2s,
color 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
}
.segmented button[aria-pressed='true'] {
background: var(--accent);
color: var(--accent-ink);
}
.segmented .save {
font-size: 0.72rem;
font-weight: 600;
padding: 1px 6px;
border-radius: 999px;
background: color-mix(in srgb, var(--ok) 22%, transparent);
color: var(--ok);
}
.segmented button[aria-pressed='true'] .save {
background: color-mix(in srgb, var(--accent-ink) 16%, transparent);
color: var(--accent-ink);
}
.plans {
display: flex;
flex-direction: column;
gap: 12px;
}
.plan {
border: 1px solid var(--edge);
border-radius: var(--radius);
padding: 16px;
background: var(--surface);
transition:
border-color 0.2s,
transform 0.14s var(--ease);
}
.plan:active {
transform: scale(0.99);
}
.plan.featured {
border-color: var(--edge-strong);
background: var(--surface-2);
}
.plan-top {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
}
.plan-name {
font-weight: 700;
font-size: 1.12rem;
}
.plan-price {
font-weight: 700;
font-size: 1.3rem;
letter-spacing: -0.01em;
}
.plan-price small {
font-weight: 500;
font-size: 0.8rem;
color: var(--muted);
margin-left: 3px;
}
.plan-feats {
margin: 10px 0 14px;
display: flex;
flex-direction: column;
gap: 6px;
color: var(--muted);
font-size: 0.92rem;
}
.plan-feats span {
display: flex;
align-items: center;
gap: 8px;
}
.plan-feats svg {
flex: none;
color: var(--ok);
}
.plan .btn {
width: 100%;
min-height: 48px;
padding: 13px;
}
.plan:not(.featured) .btn {
background: var(--surface-2);
color: var(--ink);
border-color: var(--edge);
}
.loading {
display: flex;
justify-content: center;
padding: 40px 0;
}
.loading .spinner {
width: 26px;
height: 26px;
border-color: var(--edge-strong);
border-top-color: var(--ink);
}
.note {
text-align: center;
color: var(--faint);
font-size: 0.84rem;
margin: 2px 0 0;
line-height: 1.45;
}
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'Ich habe schon ein TREK',
'native.connecting': 'Verbinde mit {host}',
'native.back': 'Zurück',
'native.plans.title': 'Tarif wählen',
'native.plans.sub': 'Gehostetes TREK auf deiner eigenen Adresse. Jederzeit änder- oder kündbar.',
'native.plans.monthly': 'Monatlich',
'native.plans.yearly': 'Jährlich',
'native.plans.save': '2 Monate gratis',
'native.plans.perMonth': 'pro Monat',
'native.plans.perYear': 'pro Jahr',
'native.plans.aiDocs': '{n} KI-Dokumente im Monat',
'native.plans.noAi': 'Keine KI-Dokumente',
'native.plans.storage': '{n} GB Speicher',
'native.plans.unlimited': 'Unbegrenzt Orte, Reisen und Personen',
'native.plans.subscribe': 'Abonnieren',
'native.plans.loadError': 'Die Tarife konnten nicht geladen werden. Prüfe deine Verbindung und versuch es erneut.',
'native.plans.retry': 'Erneut versuchen',
'native.plans.soon': 'In-App-Abos kommen bald. Vorerst auf der Website abschließen.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -41,6 +41,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -40,6 +40,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;
+15
View File
@@ -39,6 +39,21 @@ const native: TranslationStrings = {
'native.cloud.haveServer': 'I already have a TREK',
'native.connecting': 'Connecting to {host}',
'native.back': 'Back',
'native.plans.title': 'Choose your plan',
'native.plans.sub': 'Hosted TREK on your own address. Change or cancel anytime.',
'native.plans.monthly': 'Monthly',
'native.plans.yearly': 'Yearly',
'native.plans.save': '2 months free',
'native.plans.perMonth': 'per month',
'native.plans.perYear': 'per year',
'native.plans.aiDocs': '{n} AI documents a month',
'native.plans.noAi': 'No AI documents',
'native.plans.storage': '{n} GB storage',
'native.plans.unlimited': 'Unlimited places, trips and people',
'native.plans.subscribe': 'Subscribe',
'native.plans.loadError': 'Could not load the plans. Check your connection and try again.',
'native.plans.retry': 'Try again',
'native.plans.soon': 'In-app subscriptions are coming. For now, finish on the website.',
};
export default native;