feat(native): the in-app TREK Cloud subscription flow

After picking a plan, the app now carries the whole purchase itself when the build
can take one: choose your address (checked for free as you type, against the same
endpoint the website uses), your email, and subscribe. The receipt goes to
/api/iap/redeem, and a Setting up your TREK screen waits on the new address and
loads it the moment it answers.

The purchase sits behind an IapClient seam (src/native/iap.ts): a fake drives the
whole flow in development and preview, the native client reports itself
unavailable until the store plugin and products exist, and until then the plan
picker still finishes on the web, so nothing half-wired faces a customer. New
claim and provisioning strings across all locales (en, de and the five largest
European languages translated).
This commit is contained in:
Maurice
2026-11-05 14:36:21 +01:00
parent d02e58387d
commit 037e739807
31 changed files with 914 additions and 5 deletions
+254 -5
View File
@@ -5,6 +5,7 @@ import { StatusBar, Style } from '@capacitor/status-bar'
import { TrekShell } from '../src/native/trekShell'
import { probeDemo, probeServer, type ProbeFailure, type ProbeGet } from '../src/native/serverAddress'
import { CLOUD_URL, PRIVACY_URL, SERVER_DOCS_URL } from '../src/native/links'
import { getIapClient, type Purchase, productIdFor } from '../src/native/iap'
import { pickLocale, translator } from './i18n'
import logoUrl from '../public/icons/icon-white.svg'
import './shell.css'
@@ -24,6 +25,19 @@ document.documentElement.dir = locale === 'ar' ? 'rtl' : 'ltr'
const root = document.getElementById('shell')!
const PROBE_TIMEOUT_MS = 8000
const iap = getIapClient()
/** Whether this build can take a payment in-app; decided once at start. Until then, and in a store build without the purchase plugin, the plan picker finishes on the web. */
let iapAvailable = false
/** The subdomain suffix a cloud instance sits on, from the cloud address: `liketrek.com` → `.liketrek.com`. */
function cloudSuffix(): string {
try {
return `.${new URL(CLOUD_URL).host}`
} catch {
return '.liketrek.com'
}
}
// Native HTTP rather than fetch: the probe goes to a server of the user's
// choosing, which has no reason to answer this page's origin with CORS.
const get: ProbeGet = async (url) => {
@@ -302,11 +316,15 @@ async function fetchPlans(): Promise<CloudPlan[] | null> {
}
}
function subscribe(plan: CloudPlan): void {
function subscribe(plan: CloudPlan, interval: 'month' | 'year'): 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')
// In-app purchase when this build can take one; otherwise finish on the web, so
// nothing half-wired faces a customer until the store products exist.
if (iapAvailable) {
showClaim(plan, interval)
} else {
window.open(`${CLOUD_URL}/order?plan=${encodeURIComponent(plan.id)}`, '_blank', 'noopener')
}
}
function planCard(plan: CloudPlan, interval: 'month' | 'year', featured: boolean): HTMLElement {
@@ -314,7 +332,7 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', featured: boolean
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))
sub.addEventListener('click', () => subscribe(plan, interval))
return el(
'div',
{ className: featured ? 'plan featured' : 'plan' },
@@ -399,6 +417,234 @@ function showPlans(opts: { back?: boolean } = {}): void {
void load()
}
// ── Claim a TREK Cloud instance (choose address, buy, provision) ────────────
type SlugStatus = 'available' | 'taken' | 'invalid' | 'reserved' | 'error'
async function slugStatus(slug: string): Promise<SlugStatus> {
try {
const res = await get(`${CLOUD_URL}/api/slug/check?slug=${encodeURIComponent(slug)}`)
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
const status = (data as { status?: string })?.status
if (status === 'available' || status === 'taken' || status === 'invalid' || status === 'reserved') return status
return 'error'
} catch {
return 'error'
}
}
interface RedeemOk {
ok: true
instanceUrl: string
}
async function redeemPurchase(slug: string, email: string, purchase: Purchase): Promise<RedeemOk | { ok: false; code: string }> {
try {
const res = await CapacitorHttp.post({
url: `${CLOUD_URL}/api/iap/redeem`,
headers: { 'content-type': 'application/json' },
connectTimeout: PROBE_TIMEOUT_MS,
readTimeout: PROBE_TIMEOUT_MS,
data: { platform: purchase.platform, productId: purchase.productId, receipt: purchase.receipt, slug, email },
})
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
const body = (data ?? {}) as { instanceUrl?: string; code?: string }
if ((res.status === 200 || res.status === 201) && typeof body.instanceUrl === 'string') {
return { ok: true, instanceUrl: body.instanceUrl }
}
return { ok: false, code: body.code ?? 'redeem-failed' }
} catch {
return { ok: false, code: 'redeem-failed' }
}
}
function showClaim(plan: CloudPlan, interval: 'month' | 'year', opts: { back?: boolean } = {}): void {
const price = interval === 'year' ? plan.yearlyCents : plan.monthlyCents
const slugInput = el('input', { type: 'text', className: 'slug-input', autocapitalize: 'off', spellcheck: false, value: '' })
slugInput.setAttribute('inputmode', 'url')
slugInput.setAttribute('autocomplete', 'off')
slugInput.setAttribute('maxlength', '40')
slugInput.setAttribute('aria-label', t('native.claim.addressLabel'))
const slugField = el('div', { className: 'field slug-field' }, slugInput, el('span', { className: 'slug-suffix' }, cloudSuffix()))
const slugNote = el('p', { className: 'hint' })
const emailInput = el('input', { type: 'email', value: '', autocapitalize: 'off', spellcheck: false })
emailInput.setAttribute('inputmode', 'email')
emailInput.setAttribute('autocomplete', 'email')
emailInput.setAttribute('enterkeyhint', 'go')
emailInput.setAttribute('aria-label', t('native.claim.emailLabel'))
const emailField = el('div', { className: 'field' }, emailInput)
const submit = el('button', { type: 'submit', className: 'btn' }, t('native.claim.subscribe', { price: money(price) }))
const error = el('p', { className: 'error', role: 'alert' })
const form = el(
'form',
{ className: 'form', noValidate: true },
slugField,
slugNote,
emailField,
el('p', { className: 'hint' }, t('native.claim.emailHint')),
submit,
error,
)
let status: SlugStatus | null = null
let timer: ReturnType<typeof setTimeout> | undefined
const noteFor: Partial<Record<SlugStatus, string>> = {
available: t('native.claim.free'),
taken: t('native.claim.taken'),
invalid: t('native.claim.invalid'),
reserved: t('native.claim.reserved'),
}
slugInput.addEventListener('input', () => {
clearTimeout(timer)
status = null
slugNote.textContent = ''
slugNote.className = 'hint'
slugField.classList.remove('bad')
const value = slugInput.value.trim().toLowerCase()
if (value.length < 3) return
timer = setTimeout(async () => {
const result = await slugStatus(value)
if (slugInput.value.trim().toLowerCase() !== value) return
status = result
slugNote.textContent = noteFor[result] ?? ''
slugNote.className = `hint ${result === 'available' ? 'ok' : result === 'error' ? '' : 'bad'}`
}, 350)
})
const shake = (field: HTMLElement): void => {
void notify(NotificationType.Error)
field.classList.add('bad')
setTimeout(() => field.classList.remove('bad'), 350)
}
form.addEventListener('submit', async (event) => {
event.preventDefault()
error.textContent = ''
const slug = slugInput.value.trim().toLowerCase()
const email = emailInput.value.trim()
if (slug.length < 3 || status === 'taken' || status === 'invalid' || status === 'reserved') {
shake(slugField)
slugInput.focus()
return
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
shake(emailField)
emailInput.focus()
return
}
void tap(ImpactStyle.Medium)
const lock = (on: boolean) => {
slugInput.disabled = on
emailInput.disabled = on
submit.disabled = on
}
lock(true)
submit.replaceChildren(el('span', { className: 'spinner' }), t('native.connect.checking'))
const restore = () => {
lock(false)
submit.replaceChildren(t('native.claim.subscribe', { price: money(price) }))
}
const outcome = await iap.purchase(productIdFor(plan.id, interval))
if (outcome.kind === 'cancelled') {
restore()
error.textContent = t('native.claim.cancelled')
return
}
if (outcome.kind === 'failed') {
void notify(NotificationType.Error)
restore()
error.textContent = t('native.claim.failed')
return
}
const redeemed = await redeemPurchase(slug, email, outcome.purchase)
if (redeemed.ok) {
showProvisioning(redeemed.instanceUrl)
return
}
void notify(NotificationType.Error)
restore()
if (redeemed.code === 'slug-taken') {
shake(slugField)
slugNote.textContent = t('native.claim.taken')
slugNote.className = 'hint bad'
} else {
error.textContent = t('native.claim.redeemFailed')
}
})
show(
el(
'div',
{},
topbar(() => showPlans({ back: true })),
el('h1', { className: 'title-sm' }, t('native.claim.title')),
el('p', { className: 'sub' }, t('native.claim.sub')),
form,
),
opts,
)
requestAnimationFrame(() => slugInput.focus())
}
/** The build runs on the server; the app waits on the new address and loads it the moment it answers. */
function showProvisioning(instanceUrl: string): void {
const host = (() => {
try {
return new URL(instanceUrl).host
} catch {
return instanceUrl
}
})()
const spinner = el('div', { className: 'provision-mark' }, el('span', { className: 'spinner' }))
show(
el(
'div',
{ className: 'screen-center' },
spinner,
el('h1', { className: 'title-sm' }, t('native.provisioning.title')),
el('p', { className: 'sub' }, t('native.provisioning.sub', { host })),
),
)
const deadline = Date.now() + 180_000
const poll = async (): Promise<void> => {
const result = await probeServer(instanceUrl, get)
if (result.ok) {
await connected(result.url)
return
}
if (Date.now() > deadline) {
showProvisioningSlow(instanceUrl)
return
}
setTimeout(() => void poll(), 4000)
}
setTimeout(() => void poll(), 3000)
}
function showProvisioningSlow(instanceUrl: string): void {
void notify(NotificationType.Warning)
const retry = el('button', { type: 'button', className: 'btn' }, t('native.provisioning.retry'))
retry.addEventListener('click', () => {
void tap()
showProvisioning(instanceUrl)
})
show(
el(
'div',
{ className: 'screen-center' },
el('h1', { className: 'title-sm' }, t('native.provisioning.failedTitle')),
el('p', { className: 'sub' }, t('native.provisioning.failedSub')),
retry,
),
)
}
// ── Shared actions ──────────────────────────────────────────────────────────
async function runDemo(button: HTMLButtonElement, error?: HTMLElement): Promise<void> {
void tap()
@@ -487,6 +733,9 @@ async function paintStatusBar(): Promise<void> {
async function start(): Promise<void> {
void paintStatusBar()
iap.isAvailable().then((ok) => {
iapAvailable = ok
})
const { url } = await TrekShell.getServer()
if (!url) {
showWelcome()
+58
View File
@@ -652,3 +652,61 @@ a.linkbtn {
margin: 2px 0 0;
line-height: 1.45;
}
/* ── Claim: address + email, and the provisioning wait ───────────────────── */
.slug-field {
display: flex;
align-items: center;
padding-right: 14px;
}
.slug-field .slug-input {
flex: 1;
min-width: 0;
text-align: right;
}
.slug-suffix {
flex: none;
color: var(--muted);
font-size: 0.98rem;
padding-left: 2px;
user-select: none;
}
.hint {
margin: -6px 2px 2px;
font-size: 0.84rem;
color: var(--faint);
min-height: 1.1em;
transition: color 0.2s;
}
.hint.ok {
color: var(--ok);
}
.hint.bad {
color: var(--danger);
}
.screen-center {
justify-content: center;
text-align: center;
align-items: center;
gap: 18px;
}
.screen-center .title-sm,
.screen-center .sub {
text-align: center;
}
.provision-mark {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
}
.provision-mark .spinner {
width: 40px;
height: 40px;
border-width: 3px;
border-color: var(--edge-strong);
border-top-color: var(--accent);
}
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import { fakeIapClient, nativeIapClient, productIdFor } from './iap';
describe('in-app purchase client', () => {
it('names store products by the shared scheme', () => {
expect(productIdFor('start', 'month')).toBe('cloud.start.monthly');
expect(productIdFor('pro', 'year')).toBe('cloud.pro.yearly');
});
it('the native client is unavailable until the purchase plugin is wired', async () => {
expect(await nativeIapClient.isAvailable()).toBe(false);
expect(await nativeIapClient.purchase('cloud.plus.monthly')).toEqual({ kind: 'failed' });
});
it('the fake client yields a receipt for the product asked for', async () => {
expect(await fakeIapClient.isAvailable()).toBe(true);
const outcome = await fakeIapClient.purchase('cloud.plus.monthly');
expect(outcome.kind).toBe('bought');
if (outcome.kind !== 'bought') return;
expect(outcome.purchase.productId).toBe('cloud.plus.monthly');
expect(outcome.purchase.platform).toBe('apple');
expect(outcome.purchase.receipt).toContain('cloud.plus.monthly');
});
});
+92
View File
@@ -0,0 +1,92 @@
/**
* In-app purchase, from the app's side. A seam, like {@link TrekShell}: the
* screens drive a plan to a receipt through this, and never touch the store
* plugin directly. Two implementations stand behind it — a fake one for
* development and preview, so the whole subscription flow can be walked without a
* store, and the native one that will call the platform plugin once the store
* products and accounts exist. Until then the native client reports itself
* unavailable and the plan picker falls back to finishing on the web, so nothing
* half-wired ever faces a customer.
*/
export type IapPlatform = 'apple' | 'google';
/** The receipt a completed purchase yields, in the shape `/api/iap/redeem` takes. */
export interface Purchase {
platform: IapPlatform;
productId: string;
receipt: string;
}
/** What a purchase attempt came to. `cancelled` is the customer backing out, not an error. */
export type PurchaseOutcome =
| { kind: 'bought'; purchase: Purchase }
| { kind: 'cancelled' }
| { kind: 'failed' };
export interface IapClient {
/** Whether this build can take a payment: the plugin is present and the store products exist. */
isAvailable(): Promise<boolean>;
/** Runs the platform's purchase sheet for a product and resolves with what became of it. */
purchase(productId: string): Promise<PurchaseOutcome>;
}
/**
* The store product id for a plan and interval. Must match the identifiers created
* in App Store Connect and the Play Console, and the control plane's catalog
* (`cloud.<plan>.<monthly|yearly>`); kept here as the one place the app names them.
*/
export function productIdFor(planId: string, interval: 'month' | 'year'): string {
return `cloud.${planId}.${interval === 'year' ? 'yearly' : 'monthly'}`;
}
/**
* The fake used in development and preview: it waits a beat, as a real sheet
* would, then returns a receipt the control plane's fake verifier accepts. Never
* reached in a store build.
*/
export const fakeIapClient: IapClient = {
async isAvailable() {
return true;
},
async purchase(productId) {
await new Promise((resolve) => setTimeout(resolve, 900));
return {
kind: 'bought',
purchase: { platform: 'apple', productId, receipt: `fake-receipt:${productId}:${Date.now()}` },
};
},
};
/**
* The store client. A stub until the purchase plugin is a dependency and the
* products exist: it reports itself unavailable, so the plan picker finishes on
* the web instead of offering a payment it cannot take. When the plugin lands,
* this is where it is wired; the screens do not change.
*/
export const nativeIapClient: IapClient = {
async isAvailable() {
return false;
},
async purchase() {
return { kind: 'failed' };
},
};
/**
* Whether to use the fake. On in a development build, and switchable on in a
* preview build with `localStorage['trek-iap-fake'] = '1'`, so the flow can be
* shown without a store. Off in a store build, where the native client decides.
*/
function useFake(): boolean {
try {
if (import.meta.env?.DEV) return true;
return typeof localStorage !== 'undefined' && localStorage.getItem('trek-iap-fake') === '1';
} catch {
return false;
}
}
export function getIapClient(): IapClient {
return useFake() ? fakeIapClient : nativeIapClient;
}
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Deine TREK einrichten',
'native.claim.sub': 'Wähle deine Adresse. Wir richten sie sofort ein.',
'native.claim.addressLabel': 'Deine Adresse',
'native.claim.taken': 'Diese Adresse ist vergeben.',
'native.claim.invalid': 'Mindestens drei Buchstaben, Ziffern und Bindestriche.',
'native.claim.reserved': 'Diese Adresse ist reserviert.',
'native.claim.free': 'Verfügbar',
'native.claim.emailLabel': 'E-Mail',
'native.claim.emailHint': 'Für dein Konto und die Belege.',
'native.claim.subscribe': 'Abonnieren für {price}',
'native.claim.cancelled': 'Kauf abgebrochen.',
'native.claim.failed': 'Der Kauf hat nicht geklappt. Bitte versuch es erneut.',
'native.claim.redeemFailed': 'Wir konnten deine TREK nicht einrichten. Bitte versuch es erneut.',
'native.provisioning.title': 'Deine TREK wird eingerichtet',
'native.provisioning.sub': '{host} wird vorbereitet. Das dauert nur eine Minute.',
'native.provisioning.failedTitle': 'Gleich fertig',
'native.provisioning.failedSub': 'Deine TREK ist unterwegs. Versuch es gleich noch einmal oder öffne die App später.',
'native.provisioning.retry': 'Jetzt versuchen',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'native.plans.loadError': 'No se han podido cargar los planes. Revisa tu conexión e inténtalo de nuevo.',
'native.plans.retry': 'Reintentar',
'native.plans.soon': 'Las suscripciones dentro de la app llegarán pronto. Por ahora, termina en la web.',
'native.claim.title': 'Crea tu TREK',
'native.claim.sub': 'Elige tu dirección. La preparamos al instante.',
'native.claim.addressLabel': 'Tu dirección',
'native.claim.taken': 'Esa dirección ya está ocupada.',
'native.claim.invalid': 'Tres o más letras, números y guiones.',
'native.claim.reserved': 'Esa dirección está reservada.',
'native.claim.free': 'Disponible',
'native.claim.emailLabel': 'Correo',
'native.claim.emailHint': 'Para tu cuenta y los recibos.',
'native.claim.subscribe': 'Suscribirse por {price}',
'native.claim.cancelled': 'Compra cancelada.',
'native.claim.failed': 'La compra no se completó. Inténtalo de nuevo.',
'native.claim.redeemFailed': 'No pudimos preparar tu TREK. Inténtalo de nuevo.',
'native.provisioning.title': 'Preparando tu TREK',
'native.provisioning.sub': 'Preparando {host}. Solo tarda un minuto.',
'native.provisioning.failedTitle': 'Casi listo',
'native.provisioning.failedSub': 'Tu TREK está en camino. Inténtalo en un momento o abre la app más tarde.',
'native.provisioning.retry': 'Probar ahora',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Impossible de charger les forfaits. Vérifiez votre connexion et réessayez.',
'native.plans.retry': 'Réessayer',
'native.plans.soon': 'Les abonnements dans l’app arrivent bientôt. Pour l’instant, terminez sur le site.',
'native.claim.title': 'Créez votre TREK',
'native.claim.sub': 'Choisissez votre adresse. Nous la préparons aussitôt.',
'native.claim.addressLabel': 'Votre adresse',
'native.claim.taken': 'Cette adresse est déjà prise.',
'native.claim.invalid': 'Au moins trois lettres, chiffres ou traits d’union.',
'native.claim.reserved': 'Cette adresse est réservée.',
'native.claim.free': 'Disponible',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Pour votre compte et vos reçus.',
'native.claim.subscribe': 'S’abonner pour {price}',
'native.claim.cancelled': 'Achat annulé.',
'native.claim.failed': 'L’achat n’a pas abouti. Réessayez.',
'native.claim.redeemFailed': 'Nous n’avons pas pu préparer votre TREK. Réessayez.',
'native.provisioning.title': 'Préparation de votre TREK',
'native.provisioning.sub': 'Préparation de {host}. Cela ne prend qu’une minute.',
'native.provisioning.failedTitle': 'Presque prêt',
'native.provisioning.failedSub': 'Votre TREK arrive. Réessayez dans un instant ou rouvrez l’app plus tard.',
'native.provisioning.retry': 'Réessayer',
};
export default native;
+18
View File
@@ -56,6 +56,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Impossibile caricare i piani. Controlla la connessione e riprova.',
'native.plans.retry': 'Riprova',
'native.plans.soon': 'Gli abbonamenti nell’app arrivano presto. Per ora, completa sul sito.',
'native.claim.title': 'Crea il tuo TREK',
'native.claim.sub': 'Scegli il tuo indirizzo. Lo prepariamo subito.',
'native.claim.addressLabel': 'Il tuo indirizzo',
'native.claim.taken': 'Questo indirizzo è già occupato.',
'native.claim.invalid': 'Almeno tre lettere, numeri o trattini.',
'native.claim.reserved': 'Questo indirizzo è riservato.',
'native.claim.free': 'Disponibile',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'Per il tuo account e le ricevute.',
'native.claim.subscribe': 'Abbonati per {price}',
'native.claim.cancelled': 'Acquisto annullato.',
'native.claim.failed': 'L’acquisto non è andato a buon fine. Riprova.',
'native.claim.redeemFailed': 'Non siamo riusciti a preparare il tuo TREK. Riprova.',
'native.provisioning.title': 'Preparazione del tuo TREK',
'native.provisioning.sub': 'Stiamo preparando {host}. Ci vuole solo un minuto.',
'native.provisioning.failedTitle': 'Quasi pronto',
'native.provisioning.failedSub': 'Il tuo TREK sta arrivando. Riprova tra poco o riapri l’app più tardi.',
'native.provisioning.retry': 'Riprova',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Kan de abonnementen niet laden. Controleer je verbinding en probeer het opnieuw.',
'native.plans.retry': 'Opnieuw proberen',
'native.plans.soon': 'Abonneren in de app komt eraan. Rond het voorlopig af op de website.',
'native.claim.title': 'Maak je TREK',
'native.claim.sub': 'Kies je adres. We zetten het meteen klaar.',
'native.claim.addressLabel': 'Je adres',
'native.claim.taken': 'Dit adres is al bezet.',
'native.claim.invalid': 'Minstens drie letters, cijfers of streepjes.',
'native.claim.reserved': 'Dit adres is gereserveerd.',
'native.claim.free': 'Beschikbaar',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Voor je account en bonnen.',
'native.claim.subscribe': 'Abonneren voor {price}',
'native.claim.cancelled': 'Aankoop geannuleerd.',
'native.claim.failed': 'De aankoop is niet gelukt. Probeer het opnieuw.',
'native.claim.redeemFailed': 'We konden je TREK niet klaarzetten. Probeer het opnieuw.',
'native.provisioning.title': 'Je TREK wordt klaargezet',
'native.provisioning.sub': '{host} wordt klaargemaakt. Dit duurt maar een minuut.',
'native.provisioning.failedTitle': 'Bijna klaar',
'native.provisioning.failedSub': 'Je TREK is onderweg. Probeer het zo opnieuw of open de app later.',
'native.provisioning.retry': 'Nu proberen',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Nie udało się wczytać planów. Sprawdź połączenie i spróbuj ponownie.',
'native.plans.retry': 'Spróbuj ponownie',
'native.plans.soon': 'Subskrypcje w aplikacji już wkrótce. Na razie dokończ na stronie.',
'native.claim.title': 'Utwórz swój TREK',
'native.claim.sub': 'Wybierz swój adres. Przygotujemy go od razu.',
'native.claim.addressLabel': 'Twój adres',
'native.claim.taken': 'Ten adres jest już zajęty.',
'native.claim.invalid': 'Co najmniej trzy litery, cyfry lub myślniki.',
'native.claim.reserved': 'Ten adres jest zarezerwowany.',
'native.claim.free': 'Dostępny',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Do Twojego konta i potwierdzeń.',
'native.claim.subscribe': 'Subskrybuj za {price}',
'native.claim.cancelled': 'Zakup anulowany.',
'native.claim.failed': 'Zakup się nie powiódł. Spróbuj ponownie.',
'native.claim.redeemFailed': 'Nie udało się przygotować Twojego TREK. Spróbuj ponownie.',
'native.provisioning.title': 'Przygotowujemy Twój TREK',
'native.provisioning.sub': 'Przygotowujemy {host}. To zajmie tylko minutę.',
'native.provisioning.failedTitle': 'Już prawie',
'native.provisioning.failedSub': 'Twój TREK jest w drodze. Spróbuj za chwilę lub otwórz aplikację później.',
'native.provisioning.retry': 'Spróbuj teraz',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -55,6 +55,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;
+18
View File
@@ -54,6 +54,24 @@ const native: TranslationStrings = {
'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.',
'native.claim.title': 'Create your TREK',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
'native.claim.taken': 'That address is taken.',
'native.claim.invalid': 'Three or more letters, numbers and hyphens.',
'native.claim.reserved': 'That address is reserved.',
'native.claim.free': 'Available',
'native.claim.emailLabel': 'Email',
'native.claim.emailHint': 'For your account and receipts.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.provisioning.title': 'Setting up your TREK',
'native.provisioning.sub': 'Getting {host} ready. This only takes a minute.',
'native.provisioning.failedTitle': 'Almost ready',
'native.provisioning.failedSub': 'Your TREK is on its way. Try again in a moment, or open the app later.',
'native.provisioning.retry': 'Try now',
};
export default native;