diff --git a/.gitignore b/.gitignore index 760cbd3..09aa1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ dist/ +.wrangler/ # Autogenerated by esbuild. workers-site/index.js node_modules/ diff --git a/README.md b/README.md index 5a60891..508fcbb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ npm i Use `npx wrangler dev` for localhost development and for testing using Cloudflare dev tools. +``` +curl -H "X-OM-DataVersion: 241001" -H "X-OM-AppVersion: 2024.10.22-10-Google" -H 'Accept-Language: fr-FR' http://localhost:8787/maps +``` + ## Update node dependencies to their major versions ```bash diff --git a/package.json b/package.json index f6e70fd..bb395d6 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "scripts": { "build": "esbuild src/index.ts --bundle --outfile=dist/index.js", "test": "jest", - "format": "prettier --write '{src,test}/**/*.{ts,tsx}' '*.json' '*.toml' '.github/**/*.yml'", - "format:ci": "prettier --check '{src,test}/**/*.{ts,tsx}' '*.json' '*.toml' '.github/**/*.yml'", + "format": "prettier --write '{src,test}/**/*.{ts,tsx,json}' '*.json' '*.toml' '.github/**/*.yml'", + "format:ci": "prettier --check '{src,test}/**/*.{ts,tsx,json}' '*.json' '*.toml' '.github/**/*.yml'", "upgrade": "npx npm-check-updates -u && npm install", "logs": "npx wrangler tail --env prod --format json" }, diff --git a/src/locales.json b/src/locales.json new file mode 100644 index 0000000..bbc2717 --- /dev/null +++ b/src/locales.json @@ -0,0 +1,44 @@ +{ + "en": { + "placePagePrompt": "Organic Maps app is free for everyone, thanks to your donations. No ads. No trackers. Open source.", + "perMonth": "/month", + "perYear": "/year", + "otherAmount": "Other" + }, + "de": { + "placePagePrompt": "Organic Maps ist dank deiner Spenden für alle kostenlos. Keine Werbung. Keine Tracker. Open Source.", + "perMonth": "/Monat", + "perYear": "/Jahr", + "otherAmount": "Andere" + }, + "fr": { + "placePagePrompt": "L'application Organic Maps est gratuite pour tout le monde grâce à vos dons. Pas de publicité. Pas de trackers. Open-source.", + "perMonth": "/mois", + "perYear": "/an", + "otherAmount": "Autre" + }, + "nl": { + "placePagePrompt": "De Organic Maps app is gratis voor iedereen dankzij jullie donaties. Geen advertenties. Geen trackers. Open-source.", + "perMonth": "/maand", + "perYear": "/jaar", + "otherAmount": "Ander" + }, + "it": { + "placePagePrompt": "L'app Organic Maps è gratuita per tutti grazie alle vostre donazioni. Nessuna pubblicità. Nessun tracker. Open-source.", + "perMonth": "/mese", + "perYear": "/anno", + "otherAmount": "Altro" + }, + "es": { + "placePagePrompt": "Organic Maps es gratis para todos gracias a sus donaciones. Sin anuncios. Sin rastreadores. Código abierto.", + "perMonth": "/mes", + "perYear": "/año", + "otherAmount": "Otro" + }, + "pt": { + "placePagePrompt": "O app Organic Maps é gratuito para todos graças às suas doações. Sem anúncios. Sem rastreadores. Código aberto.", + "perMonth": "/mês", + "perYear": "/ano", + "otherAmount": "Outro" + } +} diff --git a/src/locales.ts b/src/locales.ts new file mode 100644 index 0000000..06a1cbe --- /dev/null +++ b/src/locales.ts @@ -0,0 +1,15 @@ +import LOCALES_JSON from './locales.json'; + +export interface Locale { + placePagePrompt: string; + perMonth: string; + perYear: string; + otherAmount: string; +} + +export interface Locales { + [key: string]: Locale; +} + +const LOCALES = LOCALES_JSON as Locales; +export default LOCALES; diff --git a/src/products.json b/src/products.json new file mode 100644 index 0000000..3bfc808 --- /dev/null +++ b/src/products.json @@ -0,0 +1,198 @@ +{ + "FR": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "DE": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "NL": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "IT": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "ES": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "PT": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "BE": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "AT": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "LU": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "MC": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "AD": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "SM": [ + { + "title": "4,99€$per_month", + "link": "https://donate.stripe.com/6oEg2912f2c81r200j" + }, + { + "title": "34,99€$per_year", + "link": "https://donate.stripe.com/eVa2bjdP19EA0mY6oI" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/28odU16mzg2Yc5GfYY" + } + ], + "GB": [ + { + "title": "£4.99$per_month", + "link": "https://donate.stripe.com/8wMg29fX98Awd9K28u" + }, + { + "title": "£34.99$per_year", + "link": "https://donate.stripe.com/eVabLT9yL2c89Xy7sP" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/6oE7vD26j3gc2v69AC" + } + ], + "US": [ + { + "title": "$5.49$per_month", + "link": "https://donate.stripe.com/00g3fncKXcQMedO5kL" + }, + { + "title": "$36.99$per_year", + "link": "https://donate.stripe.com/bIY6rz5ivcQM9XyaF6" + }, + { + "title": "$other", + "link": "https://donate.stripe.com/7sIcPX5ivg2Y2v6145" + } + ] +} diff --git a/src/products.ts b/src/products.ts new file mode 100644 index 0000000..446f628 --- /dev/null +++ b/src/products.ts @@ -0,0 +1,40 @@ +import LOCALES from './locales'; +import PRODUCTS_JSON from './products.json'; + +export interface Product { + title: string; + link: string; +} + +const PRODUCTS = PRODUCTS_JSON as Record; + +export interface ProductsConfig { + placePagePrompt: string; + products: Product[]; +} + +export function getProducts(locale: string | null): ProductsConfig | undefined { + if (!locale) { + return undefined; + } + const parts = locale.split(/[-_]/); + const language = parts[0].toLowerCase(); + const country = parts[1] ? parts[1].toUpperCase() : ''; + + const products = PRODUCTS[country]; + const trans = LOCALES[language]; + if (products === undefined || trans === undefined) { + return undefined; + } + + return { + placePagePrompt: trans.placePagePrompt, + products: products.map((product) => ({ + ...product, + title: product.title + .replace('$other', trans.otherAmount) + .replace('$per_month', trans.perMonth) + .replace('$per_year', trans.perYear), + })), + }; +} diff --git a/src/servers.ts b/src/servers.ts index 32ab9f9..7adaa20 100644 --- a/src/servers.ts +++ b/src/servers.ts @@ -1,4 +1,5 @@ import { parseDataVersion, parseAppVersion } from './versions'; +import { getProducts, ProductsConfig } from './products'; export const DATA_VERSIONS = [ 210529, // @@ -125,7 +126,7 @@ export async function getServersList(request: Request) { if (dataVersion === null) { // Older clients download from the archive. servers = [SERVER.backblaze]; - } else if (dataVersion == 240702 && abusedVersions.includes(request.headers.get('x-om-appversion'))) { + } else if (dataVersion == 240702 && abusedVersions.includes(request.headers.get('x-om-appversion') || 'unknown')) { // Redirect https://apps.apple.com/us/app/mapxplorer-navigation-radar/id6463052823 // who abuses our servers to a slow download "trap" node. return new Response('["https://cdn-fi2.organicmaps.app/"]', { @@ -176,6 +177,7 @@ export async function getServersList(request: Request) { DonateUrl?: string; NY?: string; }; + productsConfig?: ProductsConfig; } = { servers: servers, }; @@ -203,11 +205,14 @@ export async function getServersList(request: Request) { if (donatesEnabled) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore workarounds error TS2339: Property 'country' does not exist on type 'IncomingRequestCfProperties'. response.settings = { DonateUrl: DONATE_URL, NY: 'false', // Must be `string` instead of `bool`, otherwise clients will crash }; + if (appVersion.code >= 241022) { + const locale = request.headers.get('accept-language'); + response.productsConfig = getProducts(locale); + } } return new Response(JSON.stringify(response), { diff --git a/test/products.test.ts b/test/products.test.ts new file mode 100644 index 0000000..9d11fc3 --- /dev/null +++ b/test/products.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from '@jest/globals'; +import { getProducts } from '../src/products'; + +describe('getProducts', () => { + test('fr-FR', () => { + const fr_FR = getProducts('fr-FR'); + expect(fr_FR).toBeDefined(); + if (!fr_FR) return; + expect(fr_FR.placePagePrompt).toBe( + "L'application Organic Maps est gratuite pour tout le monde grâce à vos dons. Pas de publicité. Pas de trackers. Open-source.", + ); + expect(fr_FR.products[fr_FR.products.length - 1].title).toEqual('Autre'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 86c8874..1264083 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "preserveConstEnums": true, "sourceMap": true, "esModuleInterop": true, + "resolveJsonModule": true, "types": ["@cloudflare/workers-types"] }, "include": ["src/"],