fix(native): App Review findings on buying TREK Cloud in the app

- Orders closed at the cloud: the app shows the plans and that TREK Cloud
  opens soon, and sells only in a test build (TestFlight, App Review), whose
  sandbox purchases the cloud still sets up; a store purchase it would refuse
  is never offered. The plans arrive without prices then, the store's own are
  shown where it gives them.
- A purchase paid but never set up (Ask to Buy approved later, the app closed
  mid-purchase) is picked up by Restore purchases and goes on to the claim with
  that purchase, instead of failing; nothing is bought twice.
- The buy button names the period with the price (guideline 3.1.2).
- The privacy link opens the policy without the site's sales header.
- The privacy manifest declares what TREK Cloud keeps: email, name, the store
  purchase, photos, other user content and saved places, all for the app to
  work, linked to the account, never for tracking.
- The email hint promises messages about the contract, not receipts, which the
  store sends.
This commit is contained in:
Maurice
2026-11-07 11:48:03 +01:00
parent 354e30a075
commit 6e1c9e8e9f
35 changed files with 225 additions and 56 deletions
+85 -3
View File
@@ -32,13 +32,95 @@
</array>
</dict>
</array>
<!-- TREK sends no data to third parties and runs no trackers: all traffic
goes to the server the user chose. -->
<!-- No trackers and no advertising. Trip data goes to the server the user
connects; with TREK Cloud that server is ours, and buying TREK Cloud in
the app sends the email, the chosen address and the store's purchase to
liketrek.com. That is what is declared below, all for the app to work,
linked to the account, never for tracking. -->
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<array>
<!-- The account and contract email, given when buying TREK Cloud and when signing up. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeEmailAddress</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<!-- The name on a TREK Cloud account and of the people a trip is shared with. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeName</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<!-- The App Store purchase of TREK Cloud, verified and kept with the contract. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypePurchaseHistory</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<!-- Photos the user adds to trips and journeys on a TREK Cloud instance. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypePhotosorVideos</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<!-- Trips, places, bookings, notes and files on a TREK Cloud instance. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeOtherUserContent</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<!-- Places the user saves with their coordinates; the live location only when searching nearby. -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypePreciseLocation</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
</dict>
</plist>
+4 -2
View File
@@ -443,12 +443,14 @@ extension TrekShellPlugin {
/// know are left out, so the shell offers only what can really be bought.
@objc func storeProducts(_ call: CAPPluginCall) {
let ids = call.getArray("ids", String.self) ?? []
// TestFlight, App Review and Xcode builds buy in the sandbox; their receipt says so.
let sandbox = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
Task {
guard AppStore.canMakePayments, let products = try? await Product.products(for: ids) else {
call.resolve(["products": []])
call.resolve(["products": [], "sandbox": sandbox])
return
}
call.resolve(["products": products.map { ["id": $0.id, "displayPrice": $0.displayPrice] }])
call.resolve(["products": products.map { ["id": $0.id, "displayPrice": $0.displayPrice] }, "sandbox": sandbox])
}
}
+53 -21
View File
@@ -330,8 +330,9 @@ function showCloud(opts: { back?: boolean } = {}): void {
interface CloudPlan {
id: string;
name: string;
monthlyCents: number;
yearlyCents: number;
/** Absent while orders are closed: the cloud names its plans but no price leaves it then. */
monthlyCents?: number;
yearlyCents?: number;
aiDocuments: number;
storageGb: number;
}
@@ -350,16 +351,22 @@ const SAMPLE_PLANS: CloudPlan[] = [
{ id: 'studio', name: 'Studio', monthlyCents: 2399, yearlyCents: 23990, aiDocuments: 2000, storageGb: 1000 },
];
async function fetchPlans(): Promise<CloudPlan[] | null> {
interface CloudPlans {
plans: CloudPlan[];
/** Orders are closed: only a test build may buy, and the store charges it nothing. */
closed: boolean;
}
async function fetchPlans(): Promise<CloudPlans | 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;
if (Array.isArray(plans) && plans.length) return plans;
const body = (data ?? {}) as { plans?: CloudPlan[]; ordering?: string };
if (Array.isArray(body.plans) && body.plans.length) return { plans: body.plans, closed: body.ordering === 'closed' };
} catch {
// Fall through to the preview fallback.
}
if (import.meta.env?.DEV) return SAMPLE_PLANS;
if (import.meta.env?.DEV) return { plans: SAMPLE_PLANS, closed: false };
return null;
}
@@ -372,20 +379,26 @@ async function fetchPlans(): Promise<CloudPlan[] | null> {
interface Offer {
canBuy: boolean;
storePrices: Map<string, string> | null;
/** Orders are closed and this is no test build: the plans show, nothing is for sale yet. */
opensSoon: boolean;
}
function offerFor(offer: Offer, plan: CloudPlan, interval: 'month' | 'year'): { label: string; buyable: boolean } {
const cents = interval === 'year' ? plan.yearlyCents : plan.monthlyCents;
const ours = typeof cents === 'number' ? money(cents) : '';
const storePrice = offer.storePrices?.get(productIdFor(plan.id, interval));
if (offer.storePrices) return { label: storePrice ?? money(cents), buyable: offer.canBuy && !!storePrice };
return { label: money(cents), buyable: offer.canBuy };
if (offer.storePrices) return { label: storePrice ?? ours, buyable: offer.canBuy && !!storePrice };
return { label: ours, buyable: offer.canBuy && ours !== '' };
}
async function loadOffer(plans: CloudPlan[]): Promise<Offer> {
const canBuy = await iapReady;
if (!canBuy || iap === fakeIapClient) return { canBuy, storePrices: null };
const ids = plans.flatMap((p) => [productIdFor(p.id, 'month'), productIdFor(p.id, 'year')]);
return { canBuy, storePrices: await iap.prices(ids) };
async function loadOffer(list: CloudPlans): Promise<Offer> {
const available = await iapReady;
// Closed: a store purchase would be refused at the cloud, so only a test build may buy.
const opensSoon = available && list.closed && !(await iap.isSandbox().catch(() => false));
const canBuy = available && !opensSoon;
if (!canBuy || iap === fakeIapClient) return { canBuy, storePrices: null, opensSoon };
const ids = list.plans.flatMap((p) => [productIdFor(p.id, 'month'), productIdFor(p.id, 'year')]);
return { canBuy, storePrices: await iap.prices(ids), opensSoon };
}
function planCard(plan: CloudPlan, interval: 'month' | 'year', offer: Offer): HTMLElement {
@@ -400,7 +413,7 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', offer: Offer): HT
sub = el('button', { type: 'button', className: 'btn' }, t('native.plans.subscribe'));
sub.addEventListener('click', () => {
void tap(ImpactStyle.Medium);
showClaim(plan, interval, label);
showClaim(plan, interval, `${label} ${per}`);
});
}
return el(
@@ -410,7 +423,8 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', offer: Offer): HT
'div',
{ className: 'plan-top' },
el('span', { className: 'plan-name' }, plan.name),
el('span', { className: 'plan-price' }, label, el('small', {}, per))
// No price yet (orders not open): the plan shows without one rather than with an empty one.
...(label ? [el('span', { className: 'plan-price' }, label, el('small', {}, per))] : [])
),
el(
'div',
@@ -431,7 +445,8 @@ function planCard(plan: CloudPlan, interval: 'month' | 'year', offer: Offer): HT
* holds (a reinstall, a new phone). Without one: only that buying in the app is
* not available, and no pointer elsewhere.
*/
function planFooter(offer: Offer): Node[] {
function planFooter(offer: Offer, plans: CloudPlan[]): Node[] {
if (offer.opensSoon) return [el('p', { className: 'note' }, t('native.plans.closed'))];
if (!offer.canBuy) return [el('p', { className: 'note' }, t('native.plans.soon'))];
const status = el('p', { className: 'hint', role: 'status' });
@@ -454,6 +469,18 @@ function planFooter(offer: Offer): Node[] {
showProvisioning(redeemed.instanceUrl);
return;
}
// Paid but never set up (Ask to Buy approved later, the app closed mid-purchase):
// the claim screen takes the purchase as it is, so nothing is bought twice.
const unclaimed = redeemed.code === 'email-invalid' || (redeemed.code ?? '').startsWith('slug-');
const match = /^cloud\.([a-z0-9-]+)\.(monthly|yearly)$/.exec(purchase.productId);
const plan = match ? plans.find((p) => p.id === match[1]) : undefined;
if (unclaimed && match && plan) {
const interval = match[2] === 'yearly' ? 'year' : 'month';
const { label } = offerFor(offer, plan, interval);
const per = interval === 'year' ? t('native.plans.perYear') : t('native.plans.perMonth');
showClaim(plan, interval, `${label} ${per}`, { bought: purchase });
return;
}
void notify(NotificationType.Error);
restore.disabled = false;
status.className = 'hint bad';
@@ -476,7 +503,7 @@ function planFooter(offer: Offer): Node[] {
function showPlans(opts: { back?: boolean } = {}): void {
let interval: 'month' | 'year' = 'month';
let plans: CloudPlan[] | null = null;
let offer: Offer = { canBuy: false, storePrices: null };
let offer: Offer = { canBuy: false, storePrices: null, opensSoon: false };
const content = el('div', { className: 'loading' }, el('span', { className: 'spinner' }));
const footer = el('div', { className: 'plans-foot' });
@@ -510,9 +537,9 @@ function showPlans(opts: { back?: boolean } = {}): void {
const list = await fetchPlans();
if (list) {
offer = await loadOffer(list);
plans = list;
plans = list.plans;
renderList();
footer.replaceChildren(...planFooter(offer));
footer.replaceChildren(...planFooter(offer, list.plans));
return;
}
content.className = '';
@@ -584,9 +611,14 @@ async function redeemPurchase(slug: string, email: string, purchase: Purchase):
}
}
function showClaim(plan: CloudPlan, interval: 'month' | 'year', price: string, opts: { back?: boolean } = {}): void {
function showClaim(
plan: CloudPlan,
interval: 'month' | 'year',
price: string,
opts: { back?: boolean; bought?: Purchase } = {},
): void {
/** A purchase that went through but whose redeem did not: a retry redeems it again instead of buying twice. */
let bought: Purchase | null = null;
let bought: Purchase | null = opts.bought ?? null;
const slugInput = el('input', {
type: 'text',
+11
View File
@@ -58,6 +58,17 @@ describe('native client', () => {
expect(await nativeIapClient.isAvailable()).toBe(false);
});
it('knows a test build from a store one, and takes an error for a store build', async () => {
shell.storeProducts.mockResolvedValue({ products: [], sandbox: true });
expect(await nativeIapClient.isSandbox()).toBe(true);
expect(shell.storeProducts).toHaveBeenCalledWith({ ids: [] });
shell.storeProducts.mockResolvedValue({ products: [] });
expect(await nativeIapClient.isSandbox()).toBe(false);
shell.storeProducts.mockRejectedValue(new Error('no store'));
expect(await nativeIapClient.isSandbox()).toBe(false);
expect(await fakeIapClient.isSandbox()).toBe(true);
});
it('maps the store price strings by product', async () => {
shell.storeProducts.mockResolvedValue({ products: [{ id: 'cloud.plus.monthly', displayPrice: '9,99 €' }] });
const prices = await nativeIapClient.prices(['cloud.plus.monthly', 'cloud.pro.monthly']);
+12
View File
@@ -41,6 +41,8 @@ export interface IapClient {
* prices come from the plan list.
*/
prices(productIds: string[]): Promise<Map<string, string>>;
/** A test build (TestFlight, App Review), whose purchases cost nothing and may go ahead before the opening. */
isSandbox(): Promise<boolean>;
/** Runs the platform's purchase sheet for a product and resolves with what became of it. */
purchase(productId: string): Promise<PurchaseOutcome>;
/** Tells the store a purchase was delivered, once its instance exists. */
@@ -73,6 +75,9 @@ export const fakeIapClient: IapClient = {
async prices() {
return new Map();
},
async isSandbox() {
return true;
},
async purchase(productId) {
await new Promise((resolve) => setTimeout(resolve, 900));
const nonce = String(Date.now());
@@ -110,6 +115,13 @@ export const nativeIapClient: IapClient = {
return new Map();
}
},
async isSandbox() {
try {
return (await TrekShell.storeProducts({ ids: [] })).sandbox === true;
} catch {
return false;
}
},
async purchase(productId) {
try {
const result = await TrekShell.storePurchase({ productId });
+1 -1
View File
@@ -4,7 +4,7 @@
*/
/** The privacy policy the store listings name too. */
export const PRIVACY_URL = 'https://liketrek.com/privacy';
export const PRIVACY_URL = 'https://liketrek.com/privacy?app=1';
/** What a TREK server is and how to run one, for someone who has none yet. */
export const SERVER_DOCS_URL = 'https://github.com/liketrek/TREK#readme';
+3 -1
View File
@@ -74,7 +74,9 @@ export interface TrekShellPlugin {
}): Promise<{ running: boolean }>;
endTripDay(): Promise<void>;
/** iOS, feature storeKit: the App Store's price strings for the products it knows. */
storeProducts(options: { ids: string[] }): Promise<{ products: { id: string; displayPrice: string }[] }>;
storeProducts(options: {
ids: string[];
}): Promise<{ products: { id: string; displayPrice: string }[]; sandbox?: boolean }>;
/** iOS, feature storeKit: the purchase sheet; `jws` is the signed transaction, set when bought. */
storePurchase(options: {
productId: string;
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'تعذّر تحميل الخطط. تحقق من اتصالك وحاول مرة أخرى.',
'native.plans.retry': 'حاول مرة أخرى',
'native.plans.soon': 'الاشتراك داخل التطبيق غير متاح بعد.',
'native.plans.closed': 'يفتح TREK Cloud قريبًا. يمكنك الاشتراك هنا اعتبارًا من يوم الافتتاح.',
'native.claim.title': 'اقتربنا من الانتهاء',
'native.claim.sub': 'اختر عنوانك. سنُعدّه على الفور.',
'native.claim.addressLabel': 'عنوانك',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'هذا العنوان محجوز.',
'native.claim.free': 'متاح',
'native.claim.emailLabel': 'البريد الإلكتروني',
'native.claim.emailHint': 'لحسابك وإيصالاتك.',
'native.claim.emailHint': 'لحسابك والرسائل المتعلقة بعقدك.',
'native.claim.subscribe': 'اشترك مقابل {price}',
'native.claim.cancelled': 'تم إلغاء الشراء.',
'native.claim.failed': 'لم تتم عملية الشراء. حاول مرة أخرى.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Planları yükləmək mümkün olmadı. Bağlantınızı yoxlayın və yenidən cəhd edin.',
'native.plans.retry': 'Yenidən cəhd edin',
'native.plans.soon': 'Tətbiqdaxili abunə hələ mövcud deyil.',
'native.plans.closed': 'TREK Cloud tezliklə açılır. Açılış günündən etibarən burada abunə ola bilərsiniz.',
'native.claim.title': 'Az qaldı',
'native.claim.sub': 'Ünvanınızı seçin. Dərhal hazırlayırıq.',
'native.claim.addressLabel': 'Ünvanınız',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Bu ünvan ehtiyat saxlanılıb.',
'native.claim.free': 'Əlçatandır',
'native.claim.emailLabel': 'E-poçt',
'native.claim.emailHint': 'Hesabınız və qəbzləriniz üçün.',
'native.claim.emailHint': 'Hesabınız və müqavilənizlə bağlı bildirişlər üçün.',
'native.claim.subscribe': '{price} qarşılığında abunə ol',
'native.claim.cancelled': 'Alış ləğv edildi.',
'native.claim.failed': 'Alış baş tutmadı. Zəhmət olmasa yenidən cəhd edin.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Não foi possível carregar os planos. Verifique sua conexão e tente novamente.',
'native.plans.retry': 'Tentar de novo',
'native.plans.soon': 'A assinatura no app ainda não está disponível.',
'native.plans.closed': 'O TREK Cloud abre em breve. Você poderá assinar aqui a partir do dia da abertura.',
'native.claim.title': 'Quase lá',
'native.claim.sub': 'Escolha seu endereço. Preparamos tudo na hora.',
'native.claim.addressLabel': 'Seu endereço',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Esse endereço está reservado.',
'native.claim.free': 'Disponível',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Para sua conta e os recibos.',
'native.claim.emailHint': 'Para sua conta e avisos sobre seu contrato.',
'native.claim.subscribe': 'Assinar por {price}',
'native.claim.cancelled': 'Compra cancelada.',
'native.claim.failed': 'A compra não foi concluída. Tente novamente.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'No s’han pogut carregar els plans. Comprova la connexió i torna-ho a provar.',
'native.plans.retry': 'Torna-ho a provar',
'native.plans.soon': 'La subscripció a l’app encara no està disponible.',
'native.plans.closed': 'TREK Cloud obre aviat. Podràs subscriure’t aquí des del dia de l’obertura.',
'native.claim.title': 'Gairebé a punt',
'native.claim.sub': 'Tria la teva adreça. Ho preparem de seguida.',
'native.claim.addressLabel': 'La teva adreça',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Aquesta adreça està reservada.',
'native.claim.free': 'Disponible',
'native.claim.emailLabel': 'Correu',
'native.claim.emailHint': 'Per al teu compte i els rebuts.',
'native.claim.emailHint': 'Per al teu compte i els avisos sobre el teu contracte.',
'native.claim.subscribe': 'Subscriu-te per {price}',
'native.claim.cancelled': 'Compra cancel·lada.',
'native.claim.failed': 'La compra no s’ha completat. Torna-ho a provar.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Tarify se nepodařilo načíst. Zkontrolujte připojení a zkuste to znovu.',
'native.plans.retry': 'Zkusit znovu',
'native.plans.soon': 'Předplatné v aplikaci zatím není k dispozici.',
'native.plans.closed': 'TREK Cloud brzy otevírá. Ode dne otevření si zde můžete předplatné sjednat.',
'native.claim.title': 'Skoro hotovo',
'native.claim.sub': 'Vyberte si adresu. Hned ji připravíme.',
'native.claim.addressLabel': 'Vaše adresa',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Tato adresa je rezervovaná.',
'native.claim.free': 'Dostupné',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Pro váš účet a účtenky.',
'native.claim.emailHint': 'Pro váš účet a zprávy ke smlouvě.',
'native.claim.subscribe': 'Předplatit za {price}',
'native.claim.cancelled': 'Nákup zrušen.',
'native.claim.failed': 'Nákup se nezdařil. Zkuste to znovu.',
+2 -1
View File
@@ -55,6 +55,7 @@ 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': 'Abschließen in der App ist noch nicht verfügbar.',
'native.plans.closed': 'TREK Cloud öffnet bald. Ab dem Eröffnungstag kannst du hier abonnieren.',
'native.claim.title': 'Fast geschafft',
'native.claim.sub': 'Wähle deine Adresse. Wir richten sie sofort ein.',
'native.claim.addressLabel': 'Deine Adresse',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'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.emailHint': 'Für dein Konto und Mitteilungen zu deinem Vertrag.',
'native.claim.subscribe': 'Abonnieren für {price}',
'native.claim.cancelled': 'Kauf abgebrochen.',
'native.claim.failed': 'Der Kauf hat nicht geklappt. Bitte versuch es erneut.',
+2 -1
View File
@@ -54,6 +54,7 @@ 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': 'Subscribing in the app is not available yet.',
'native.plans.closed': 'TREK Cloud opens soon. You can subscribe here from opening day.',
'native.claim.title': 'Almost there',
'native.claim.sub': 'Choose your address. We set it up right away.',
'native.claim.addressLabel': 'Your address',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'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.emailHint': 'For your account and messages about your contract.',
'native.claim.subscribe': 'Subscribe for {price}',
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
+2 -1
View File
@@ -55,6 +55,7 @@ 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': 'La suscripción dentro de la app aún no está disponible.',
'native.plans.closed': 'TREK Cloud abre pronto. Podrás suscribirte aquí desde el día de la apertura.',
'native.claim.title': 'Casi listo',
'native.claim.sub': 'Elige tu dirección. La preparamos al instante.',
'native.claim.addressLabel': 'Tu dirección',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'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.emailHint': 'Para tu cuenta y los avisos sobre tu contrato.',
'native.claim.subscribe': 'Suscribirse por {price}',
'native.claim.cancelled': 'Compra cancelada.',
'native.claim.failed': 'La compra no se completó. Inténtalo de nuevo.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Pakette ei õnnestunud laadida. Kontrolli ühendust ja proovi uuesti.',
'native.plans.retry': 'Proovi uuesti',
'native.plans.soon': 'Rakendusesisene tellimine pole veel saadaval.',
'native.plans.closed': 'TREK Cloud avatakse peagi. Avamispäevast saad siin tellida.',
'native.claim.title': 'Peaaegu valmis',
'native.claim.sub': 'Vali oma aadress. Seadistame selle kohe.',
'native.claim.addressLabel': 'Sinu aadress',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'See aadress on reserveeritud.',
'native.claim.free': 'Saadaval',
'native.claim.emailLabel': 'E-post',
'native.claim.emailHint': 'Sinu konto ja kviitungite jaoks.',
'native.claim.emailHint': 'Sinu konto ja lepinguga seotud teadete jaoks.',
'native.claim.subscribe': 'Telli hinnaga {price}',
'native.claim.cancelled': 'Ost tühistatud.',
'native.claim.failed': 'Ost ei õnnestunud. Proovi uuesti.',
+2 -1
View File
@@ -55,6 +55,7 @@ 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': 'L’abonnement dans l’app n’est pas encore disponible.',
'native.plans.closed': 'TREK Cloud ouvre bientôt. Vous pourrez vous abonner ici dès l’ouverture.',
'native.claim.title': 'Presque terminé',
'native.claim.sub': 'Choisissez votre adresse. Nous la préparons aussitôt.',
'native.claim.addressLabel': 'Votre adresse',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'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.emailHint': 'Pour votre compte et les messages concernant votre contrat.',
'native.claim.subscribe': 'S’abonner pour {price}',
'native.claim.cancelled': 'Achat annulé.',
'native.claim.failed': 'L’achat n’a pas abouti. Réessayez.',
+2 -1
View File
@@ -57,6 +57,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Δεν ήταν δυνατή η φόρτωση των πλάνων. Ελέγξτε τη σύνδεσή σας και δοκιμάστε ξανά.',
'native.plans.retry': 'Δοκιμάστε ξανά',
'native.plans.soon': 'Η συνδρομή μέσα από την εφαρμογή δεν είναι ακόμη διαθέσιμη.',
'native.plans.closed': 'Το TREK Cloud ανοίγει σύντομα. Από την ημέρα έναρξης θα μπορείτε να εγγραφείτε εδώ.',
'native.claim.title': 'Σχεδόν έτοιμο',
'native.claim.sub': 'Επιλέξτε τη διεύθυνσή σας. Το ετοιμάζουμε αμέσως.',
'native.claim.addressLabel': 'Η διεύθυνσή σας',
@@ -65,7 +66,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Αυτή η διεύθυνση είναι δεσμευμένη.',
'native.claim.free': 'Διαθέσιμη',
'native.claim.emailLabel': 'Ηλ. ταχυδρομείο',
'native.claim.emailHint': 'Για τον λογαριασμό και τις αποδείξεις σας.',
'native.claim.emailHint': 'Για τον λογαριασμό σας και ενημερώσεις για τη σύμβασή σας.',
'native.claim.subscribe': 'Εγγραφή για {price}',
'native.claim.cancelled': 'Η αγορά ακυρώθηκε.',
'native.claim.failed': 'Η αγορά δεν ολοκληρώθηκε. Δοκιμάστε ξανά.',
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'לא ניתן לטעון את התוכניות. בדקו את החיבור ונסו שוב.',
'native.plans.retry': 'נסו שוב',
'native.plans.soon': 'הרשמה בתוך האפליקציה עדיין לא זמינה.',
'native.plans.closed': 'TREK Cloud ייפתח בקרוב. מיום הפתיחה תוכלו להירשם כאן.',
'native.claim.title': 'כמעט סיימנו',
'native.claim.sub': 'בחרו את הכתובת שלכם. אנחנו מכינים אותה מיד.',
'native.claim.addressLabel': 'הכתובת שלכם',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'הכתובת הזו שמורה.',
'native.claim.free': 'זמין',
'native.claim.emailLabel': 'אימייל',
'native.claim.emailHint': 'עבור החשבון והקבלות שלכם.',
'native.claim.emailHint': 'לחשבון שלכם ולהודעות על החוזה.',
'native.claim.subscribe': 'הרשמה תמורת {price}',
'native.claim.cancelled': 'הרכישה בוטלה.',
'native.claim.failed': 'הרכישה לא הושלמה. אנא נסו שוב.',
+2 -1
View File
@@ -55,6 +55,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'A csomagokat nem sikerült betölteni. Ellenőrizd a kapcsolatot, és próbáld újra.',
'native.plans.retry': 'Újra',
'native.plans.soon': 'Az appon belüli előfizetés még nem érhető el.',
'native.plans.closed': 'A TREK Cloud hamarosan nyit. A nyitás napjától itt tudsz előfizetni.',
'native.claim.title': 'Mindjárt kész',
'native.claim.sub': 'Válaszd ki a címed. Azonnal beállítjuk.',
'native.claim.addressLabel': 'A címed',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Ez a cím fenntartott.',
'native.claim.free': 'Szabad',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'A fiókodhoz és a számlákhoz.',
'native.claim.emailHint': 'A fiókodhoz és a szerződéssel kapcsolatos üzenetekhez.',
'native.claim.subscribe': 'Előfizetés ennyiért: {price}',
'native.claim.cancelled': 'Vásárlás megszakítva.',
'native.claim.failed': 'A vásárlás nem sikerült. Próbáld újra.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Tidak dapat memuat paket. Periksa koneksi Anda dan coba lagi.',
'native.plans.retry': 'Coba lagi',
'native.plans.soon': 'Berlangganan di aplikasi belum tersedia.',
'native.plans.closed': 'TREK Cloud segera dibuka. Mulai hari pembukaan, Anda bisa berlangganan di sini.',
'native.claim.title': 'Hampir selesai',
'native.claim.sub': 'Pilih alamat Anda. Kami langsung menyiapkannya.',
'native.claim.addressLabel': 'Alamat Anda',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Alamat itu dicadangkan.',
'native.claim.free': 'Tersedia',
'native.claim.emailLabel': 'Email', // same-as-en
'native.claim.emailHint': 'Untuk akun dan tanda terima Anda.',
'native.claim.emailHint': 'Untuk akun Anda dan pemberitahuan tentang kontrak Anda.',
'native.claim.subscribe': 'Berlangganan seharga {price}',
'native.claim.cancelled': 'Pembelian dibatalkan.',
'native.claim.failed': 'Pembelian tidak berhasil. Silakan coba lagi.',
+2 -1
View File
@@ -55,6 +55,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Impossibile caricare i piani. Controlla la connessione e riprova.',
'native.plans.retry': 'Riprova',
'native.plans.soon': 'L’abbonamento nell’app non è ancora disponibile.',
'native.plans.closed': 'TREK Cloud apre presto. Potrai abbonarti qui dal giorno dell’apertura.',
'native.claim.title': 'Quasi fatto',
'native.claim.sub': 'Scegli il tuo indirizzo. Lo prepariamo subito.',
'native.claim.addressLabel': 'Il tuo indirizzo',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Questo indirizzo è riservato.',
'native.claim.free': 'Disponibile',
'native.claim.emailLabel': 'Email', // same-as-en
'native.claim.emailHint': 'Per il tuo account e le ricevute.',
'native.claim.emailHint': 'Per il tuo account e le comunicazioni sul contratto.',
'native.claim.subscribe': 'Abbonati per {price}',
'native.claim.cancelled': 'Acquisto annullato.',
'native.claim.failed': 'L’acquisto non è andato a buon fine. Riprova.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'プランを読み込めませんでした。接続を確認してもう一度お試しください。',
'native.plans.retry': 'もう一度試す',
'native.plans.soon': 'アプリ内での登録はまだご利用いただけません。',
'native.plans.closed': 'TREK Cloudはまもなく開始します。開始日からここで登録できます。',
'native.claim.title': 'もう少しです',
'native.claim.sub': 'アドレスを選んでください。すぐに用意します。',
'native.claim.addressLabel': 'あなたのアドレス',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'このアドレスは予約済みです。',
'native.claim.free': '利用可能',
'native.claim.emailLabel': 'メール',
'native.claim.emailHint': 'アカウントと領収書のため。',
'native.claim.emailHint': 'アカウントと契約に関するお知らせ用です。',
'native.claim.subscribe': '{price} で登録',
'native.claim.cancelled': '購入をキャンセルしました。',
'native.claim.failed': '購入できませんでした。もう一度お試しください。',
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': '플랜을 불러오지 못했습니다. 연결을 확인하고 다시 시도하세요.',
'native.plans.retry': '다시 시도',
'native.plans.soon': '앱 내 구독은 아직 이용할 수 없습니다.',
'native.plans.closed': 'TREK Cloud가 곧 문을 엽니다. 오픈일부터 여기에서 구독할 수 있습니다.',
'native.claim.title': '거의 다 됐어요',
'native.claim.sub': '주소를 선택하세요. 바로 준비해 드립니다.',
'native.claim.addressLabel': '내 주소',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': '예약된 주소예요.',
'native.claim.free': '사용 가능',
'native.claim.emailLabel': '이메일',
'native.claim.emailHint': '계정과 영수증용입니다.',
'native.claim.emailHint': '계정과 계약 관련 안내에 사용됩니다.',
'native.claim.subscribe': '{price}에 구독',
'native.claim.cancelled': '구매가 취소되었습니다.',
'native.claim.failed': '구매가 완료되지 않았습니다. 다시 시도하세요.',
+2 -1
View File
@@ -54,6 +54,7 @@ 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 is nog niet beschikbaar.',
'native.plans.closed': 'TREK Cloud opent binnenkort. Vanaf de openingsdag kun je hier abonneren.',
'native.claim.title': 'Bijna klaar',
'native.claim.sub': 'Kies je adres. We zetten het meteen klaar.',
'native.claim.addressLabel': 'Je adres',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'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.emailHint': 'Voor je account en berichten over je contract.',
'native.claim.subscribe': 'Abonneren voor {price}',
'native.claim.cancelled': 'Aankoop geannuleerd.',
'native.claim.failed': 'De aankoop is niet gelukt. Probeer het opnieuw.',
+2 -1
View File
@@ -54,6 +54,7 @@ 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': 'Subskrypcja w aplikacji nie jest jeszcze dostępna.',
'native.plans.closed': 'TREK Cloud wkrótce startuje. Od dnia otwarcia możesz tu wykupić subskrypcję.',
'native.claim.title': 'Prawie gotowe',
'native.claim.sub': 'Wybierz swój adres. Przygotujemy go od razu.',
'native.claim.addressLabel': 'Twój adres',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'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.emailHint': 'Do konta i wiadomości dotyczących umowy.',
'native.claim.subscribe': 'Subskrybuj za {price}',
'native.claim.cancelled': 'Zakup anulowany.',
'native.claim.failed': 'Zakup się nie powiódł. Spróbuj ponownie.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Не удалось загрузить тарифы. Проверьте подключение и повторите попытку.',
'native.plans.retry': 'Повторить',
'native.plans.soon': 'Оформление подписки в приложении пока недоступно.',
'native.plans.closed': 'TREK Cloud скоро откроется. Оформить подписку здесь можно будет со дня открытия.',
'native.claim.title': 'Почти готово',
'native.claim.sub': 'Выберите адрес. Мы сразу всё настроим.',
'native.claim.addressLabel': 'Ваш адрес',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Этот адрес зарезервирован.',
'native.claim.free': 'Доступно',
'native.claim.emailLabel': 'Эл. почта',
'native.claim.emailHint': 'Для вашего аккаунта и чеков.',
'native.claim.emailHint': 'Для вашей учётной записи и сообщений о договоре.',
'native.claim.subscribe': 'Подписаться за {price}',
'native.claim.cancelled': 'Покупка отменена.',
'native.claim.failed': 'Покупка не прошла. Повторите попытку.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Tarify sa nepodarilo načítať. Skontrolujte pripojenie a skúste to znova.',
'native.plans.retry': 'Skúsiť znova',
'native.plans.soon': 'Predplatné v aplikácii zatiaľ nie je dostupné.',
'native.plans.closed': 'TREK Cloud čoskoro otvára. Odo dňa otvorenia si tu môžete predplatné objednať.',
'native.claim.title': 'Takmer hotovo',
'native.claim.sub': 'Vyberte si adresu. Hneď ju pripravíme.',
'native.claim.addressLabel': 'Vaša adresa',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Táto adresa je rezervovaná.',
'native.claim.free': 'Dostupné',
'native.claim.emailLabel': 'E-mail',
'native.claim.emailHint': 'Pre váš účet a účtenky.',
'native.claim.emailHint': 'Pre váš účet a správy k zmluve.',
'native.claim.subscribe': 'Predplatiť za {price}',
'native.claim.cancelled': 'Nákup zrušený.',
'native.claim.failed': 'Nákup sa nepodaril. Skúste to znova.',
+2 -1
View File
@@ -55,6 +55,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Det gick inte att läsa in abonnemangen. Kontrollera anslutningen och försök igen.',
'native.plans.retry': 'Försök igen',
'native.plans.soon': 'Prenumeration i appen är inte tillgänglig ännu.',
'native.plans.closed': 'TREK Cloud öppnar snart. Från öppningsdagen kan du prenumerera här.',
'native.claim.title': 'Nästan klart',
'native.claim.sub': 'Välj din adress. Vi gör i ordning den direkt.',
'native.claim.addressLabel': 'Din adress',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Den adressen är reserverad.',
'native.claim.free': 'Ledig',
'native.claim.emailLabel': 'E-post',
'native.claim.emailHint': 'För ditt konto och dina kvitton.',
'native.claim.emailHint': 'För ditt konto och meddelanden om ditt avtal.',
'native.claim.subscribe': 'Prenumerera för {price}',
'native.claim.cancelled': 'Köpet avbröts.',
'native.claim.failed': 'Köpet gick inte igenom. Försök igen.',
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'โหลดแพ็กเกจไม่สำเร็จ ตรวจสอบการเชื่อมต่อแล้วลองอีกครั้ง',
'native.plans.retry': 'ลองอีกครั้ง',
'native.plans.soon': 'ยังไม่สามารถสมัครสมาชิกในแอปได้',
'native.plans.closed': 'TREK Cloud จะเปิดเร็วๆ นี้ ตั้งแต่วันเปิดตัวคุณสามารถสมัครสมาชิกได้ที่นี่',
'native.claim.title': 'เกือบเสร็จแล้ว',
'native.claim.sub': 'เลือกที่อยู่ของคุณ เราจะตั้งค่าให้ทันที',
'native.claim.addressLabel': 'ที่อยู่ของคุณ',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'ที่อยู่นี้ถูกสงวนไว้',
'native.claim.free': 'ว่าง',
'native.claim.emailLabel': 'อีเมล',
'native.claim.emailHint': 'สำหรับบัญชีและใบเสร็จของคุณ',
'native.claim.emailHint': 'สำหรับบัญชีของคุณและการแจ้งเกี่ยวกับสัญญา',
'native.claim.subscribe': 'สมัครสมาชิกในราคา {price}',
'native.claim.cancelled': 'ยกเลิกการซื้อแล้ว',
'native.claim.failed': 'การซื้อไม่สำเร็จ โปรดลองอีกครั้ง',
+2 -1
View File
@@ -55,6 +55,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Planlar yüklenemedi. Bağlantınızı kontrol edip tekrar deneyin.',
'native.plans.retry': 'Tekrar dene',
'native.plans.soon': 'Uygulama içi abonelik henüz kullanılamıyor.',
'native.plans.closed': 'TREK Cloud yakında açılıyor. Açılış gününden itibaren buradan abone olabilirsiniz.',
'native.claim.title': 'Neredeyse hazır',
'native.claim.sub': 'Adresinizi seçin. Hemen hazırlıyoruz.',
'native.claim.addressLabel': 'Adresiniz',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Bu adres ayrılmış.',
'native.claim.free': 'Uygun',
'native.claim.emailLabel': 'E-posta',
'native.claim.emailHint': 'Hesabınız ve makbuzlarınız için.',
'native.claim.emailHint': 'Hesabınız ve sözleşmenizle ilgili bildirimler için.',
'native.claim.subscribe': '{price} karşılığında abone ol',
'native.claim.cancelled': 'Satın alma iptal edildi.',
'native.claim.failed': 'Satın alma tamamlanamadı. Lütfen tekrar deneyin.',
+2 -1
View File
@@ -54,6 +54,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Не вдалося завантажити тарифи. Перевірте з’єднання та повторіть спробу.',
'native.plans.retry': 'Повторити',
'native.plans.soon': 'Оформлення підписки в застосунку поки недоступне.',
'native.plans.closed': 'TREK Cloud скоро відкриється. Оформити підписку тут можна буде з дня відкриття.',
'native.claim.title': 'Майже готово',
'native.claim.sub': 'Виберіть адресу. Ми одразу все налаштуємо.',
'native.claim.addressLabel': 'Ваша адреса',
@@ -62,7 +63,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Ця адреса зарезервована.',
'native.claim.free': 'Доступно',
'native.claim.emailLabel': 'Ел. пошта',
'native.claim.emailHint': 'Для вашого облікового запису та чеків.',
'native.claim.emailHint': 'Для вашого облікового запису та повідомлень щодо договору.',
'native.claim.subscribe': 'Підписатися за {price}',
'native.claim.cancelled': 'Покупку скасовано.',
'native.claim.failed': 'Покупка не пройшла. Повторіть спробу.',
+2 -1
View File
@@ -55,6 +55,7 @@ const native: TranslationStrings = {
'native.plans.loadError': 'Không thể tải các gói. Hãy kiểm tra kết nối và thử lại.',
'native.plans.retry': 'Thử lại',
'native.plans.soon': 'Chưa thể đăng ký trong ứng dụng.',
'native.plans.closed': 'TREK Cloud sắp ra mắt. Từ ngày ra mắt, bạn có thể đăng ký tại đây.',
'native.claim.title': 'Sắp xong rồi',
'native.claim.sub': 'Chọn địa chỉ của bạn. Chúng tôi thiết lập ngay.',
'native.claim.addressLabel': 'Địa chỉ của bạn',
@@ -63,7 +64,7 @@ const native: TranslationStrings = {
'native.claim.reserved': 'Địa chỉ đó đã được giữ chỗ.',
'native.claim.free': 'Có sẵn',
'native.claim.emailLabel': 'Email', // same-as-en
'native.claim.emailHint': 'Cho tài khoản và biên nhận của bạn.',
'native.claim.emailHint': 'Dùng cho tài khoản và thông báo về hợp đồng của bạn.',
'native.claim.subscribe': 'Đăng ký với giá {price}',
'native.claim.cancelled': 'Đã hủy mua.',
'native.claim.failed': 'Giao dịch mua không thành công. Vui lòng thử lại.',
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': '無法載入方案。請檢查網路連線後重試。',
'native.plans.retry': '重試',
'native.plans.soon': '目前尚不支援在 App 內訂閱。',
'native.plans.closed': 'TREK Cloud 即將開放。開放當天起即可在此訂閱。',
'native.claim.title': '快完成了',
'native.claim.sub': '選擇你的網址。我們會立即為你準備好。',
'native.claim.addressLabel': '你的網址',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': '此網址已被保留。',
'native.claim.free': '可用',
'native.claim.emailLabel': '電子郵件',
'native.claim.emailHint': '用於你的帳戶和收據。',
'native.claim.emailHint': '用於你的帳號與合約相關通知。',
'native.claim.subscribe': '以 {price} 訂閱',
'native.claim.cancelled': '已取消購買。',
'native.claim.failed': '購買未成功。請重試。',
+2 -1
View File
@@ -53,6 +53,7 @@ const native: TranslationStrings = {
'native.plans.loadError': '无法加载方案。请检查网络连接后重试。',
'native.plans.retry': '重试',
'native.plans.soon': '暂不支持在应用内订阅。',
'native.plans.closed': 'TREK Cloud 即将开放。开放当天起即可在此订阅。',
'native.claim.title': '就快好了',
'native.claim.sub': '选择你的地址。我们会立即为你准备好。',
'native.claim.addressLabel': '你的地址',
@@ -61,7 +62,7 @@ const native: TranslationStrings = {
'native.claim.reserved': '该地址已被保留。',
'native.claim.free': '可用',
'native.claim.emailLabel': '邮箱',
'native.claim.emailHint': '用于你的账户和收据。',
'native.claim.emailHint': '用于你的账户和合同相关通知。',
'native.claim.subscribe': '以 {price} 订阅',
'native.claim.cancelled': '购买已取消。',
'native.claim.failed': '购买未成功。请重试。',