From 02534f4e8eadef669494de0309269650e021caab Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 7 Aug 2026 16:41:00 -0300 Subject: [PATCH] feat(radar): contributor + supporter claim buttons on the activation screen (#9710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(radar): add F4/T7 contributor-claim / supporter-plans link config Pure, DB-free src/lib/radar/links.ts resolves the two outbound "get a supporter key" URLs (contributor GitHub-OAuth claim + supporter plans page), same env-override pattern as RADAR_FEED_URL. No pricing/value is ever resolved here (D14) — only the link. * feat(radar): relay F4/T7 claim/plans links via GET /api/radar/settings Smallest-surface option per spec: no dedicated route. The existing settings snapshot now also returns contributorClaimUrl/supporterPlansUrl so the dashboard client never reads process.env itself. Both are plain public URLs, gated by the same flag/auth checks as the rest of the response. * feat(radar): add contributor/supporter claim buttons to activation screen F4/T7 — "I'm a contributor" opens the GitHub OAuth claim flow; "Support the project" opens the plans/payment page. Both links come from the settings fetch (never a hardcoded URL in this client component) and open in a new tab. No price/value anywhere in the copy — the destination page is the only place pricing lives (D14). i18n: 5 new radarPage keys (claimSectionTitle, contributorButton, contributorHint, supporterButton, supporterHint) added to all 43 locale files with the English copy as fallback value. * docs(radar): document F4/T7 supporter-key acquisition paths RADAR.md: new "Getting a supporter key" section covering both claim flows, the two env-var overrides, and the current gap (no dedicated key-paste input in the dashboard yet — POST /api/radar/settings is the only way to set one today). ENVIRONMENT.md + .env.example: register RADAR_CONTRIBUTOR_CLAIM_URL / RADAR_SUPPORTER_PLANS_URL for check:env-doc-sync. --------- Co-authored-by: diegosouzapw --- .env.example | 17 ++- docs/frameworks/RADAR.md | 37 ++++++ docs/reference/ENVIRONMENT.md | 17 +-- src/app/(dashboard)/dashboard/radar/page.tsx | 43 +++++++ src/app/api/radar/settings/route.ts | 10 ++ src/i18n/messages/ar.json | 5 + src/i18n/messages/az.json | 5 + src/i18n/messages/bg.json | 5 + src/i18n/messages/bn.json | 5 + src/i18n/messages/cs.json | 5 + src/i18n/messages/da.json | 5 + src/i18n/messages/de.json | 5 + src/i18n/messages/en.json | 5 + src/i18n/messages/es.json | 5 + src/i18n/messages/fa.json | 5 + src/i18n/messages/fi.json | 5 + src/i18n/messages/fr.json | 5 + src/i18n/messages/gu.json | 5 + src/i18n/messages/he.json | 5 + src/i18n/messages/hi.json | 5 + src/i18n/messages/hu.json | 5 + src/i18n/messages/id.json | 5 + src/i18n/messages/in.json | 5 + src/i18n/messages/it.json | 5 + src/i18n/messages/ja.json | 5 + src/i18n/messages/ko.json | 5 + src/i18n/messages/mr.json | 5 + src/i18n/messages/ms.json | 5 + src/i18n/messages/nl.json | 5 + src/i18n/messages/no.json | 5 + src/i18n/messages/phi.json | 5 + src/i18n/messages/pl.json | 5 + src/i18n/messages/pt-BR.json | 5 + src/i18n/messages/pt.json | 5 + src/i18n/messages/ro.json | 5 + src/i18n/messages/ru.json | 5 + src/i18n/messages/sk.json | 5 + src/i18n/messages/sv.json | 5 + src/i18n/messages/sw.json | 5 + src/i18n/messages/ta.json | 5 + src/i18n/messages/te.json | 5 + src/i18n/messages/th.json | 5 + src/i18n/messages/tr.json | 5 + src/i18n/messages/uk-UA.json | 5 + src/i18n/messages/ur.json | 5 + src/i18n/messages/vi.json | 5 + src/i18n/messages/zh-CN.json | 5 + src/i18n/messages/zh-TW.json | 5 + src/lib/radar/links.ts | 42 +++++++ tests/unit/radar-api-routes.test.ts | 26 ++++ tests/unit/radar-claim-buttons.test.ts | 122 +++++++++++++++++++ tests/unit/radar-links.test.ts | 56 +++++++++ 52 files changed, 574 insertions(+), 11 deletions(-) create mode 100644 src/lib/radar/links.ts create mode 100644 tests/unit/radar-claim-buttons.test.ts create mode 100644 tests/unit/radar-links.test.ts diff --git a/.env.example b/.env.example index e0131b2f69..2cddc9d147 100644 --- a/.env.example +++ b/.env.example @@ -2470,10 +2470,10 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis # ═══════════════════════════════════════════════════════════════════════════════ # Optional add-on (feature flag RADAR_ENABLED, default off — see feature flag # settings, not an env var) that overlays a signed, freshly-curated free-model -# catalog on top of the release baseline. Both variables below are optional and -# only needed to point the client at a self-hosted/forked feed instead of the -# default OmniRoute Radar feed. Used by: src/lib/radar/sync.ts, -# src/lib/radar/pinnedKeys.ts. +# catalog on top of the release baseline. All four variables below are optional +# and only needed to point the client at a self-hosted/forked feed or +# supporter-key flow instead of the default OmniRoute Radar service. Used by: +# src/lib/radar/sync.ts, src/lib/radar/pinnedKeys.ts, src/lib/radar/links.ts. # Base URL of the Radar feed service. Overrides the built-in default so forks # and self-hosters can point at their own signed feed. @@ -2483,3 +2483,12 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis # signature, replacing the pinned default key. Required when self-hosting a # feed signed with a different key pair. # RADAR_FEED_PUBKEY= + +# URL the dashboard's "I'm a contributor" button opens (GitHub OAuth +# supporter-key claim flow). No pricing/value lives in this repo — only the +# link. +# RADAR_CONTRIBUTOR_CLAIM_URL=https://radar.omniroute.online/auth/github + +# URL the dashboard's "Support the project" button opens (payment/plans +# page). No pricing/value lives in this repo — only the link. +# RADAR_SUPPORTER_PLANS_URL=https://radar.omniroute.online/planos diff --git a/docs/frameworks/RADAR.md b/docs/frameworks/RADAR.md index e7618cd6c2..5f4023903c 100644 --- a/docs/frameworks/RADAR.md +++ b/docs/frameworks/RADAR.md @@ -82,6 +82,43 @@ that lets the feed service decide which tier to serve (see --- +## Getting a supporter key + +The activation screen (`/dashboard/radar`) links out to two flows for **obtaining** a +supporter key. The OSS repo itself never issues one, never runs payment code, and +**never states a price** — pricing is decided and displayed entirely on the +destination pages, not in this repo (spec decision D14). + +- **"I'm a contributor"** — opens `RADAR_CONTRIBUTOR_CLAIM_URL` (default + `https://radar.omniroute.online/auth/github`), a GitHub OAuth claim flow hosted on + the private radar server. It verifies the visitor's GitHub account and grants a + supporter key to anyone with 5+ merged pull requests or a top-100 contributor spot + on the repo. +- **"Support the project"** — opens `RADAR_SUPPORTER_PLANS_URL` (default + `https://radar.omniroute.online/planos`), the payment/plans page. + +Both URLs are resolved server-side (`src/lib/radar/links.ts`, same env-override +pattern as `RADAR_FEED_URL`) and relayed to the dashboard through the existing +`GET /api/radar/settings` response (`contributorClaimUrl`, `supporterPlansUrl`) — the +client component never reads `process.env` itself. + +| Var | Purpose | +| -------------------------------- | ---------------------------------------------------------------------------------------------- | +| `RADAR_CONTRIBUTOR_CLAIM_URL` | Overrides the contributor-claim URL (default `https://radar.omniroute.online/auth/github`). | +| `RADAR_SUPPORTER_PLANS_URL` | Overrides the supporter-plans URL (default `https://radar.omniroute.online/planos`). | + +Once a visitor has a key (`omr_` + 40 hex chars), it is set with `POST +/api/radar/settings` (`{ supporterKey }`) — the same endpoint documented under +[Data sync](#data-sync-is-a-separate-opt-in--the-privacy-promise) above. + +**Known gap:** the dashboard activation screen does not yet have a dedicated +key-paste input — pasting a key today requires calling `POST /api/radar/settings` +directly (curl, a script, or a future UI). This release only adds the two claim/plans +buttons; the API already accepts and masks the key, but no `` for it exists in +`src/app/(dashboard)/dashboard/radar/page.tsx` yet. + +--- + ## Security model ### Ed25519 signature over exact bytes diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 7e93c508fe..90c8fb7590 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -1275,14 +1275,17 @@ that should be able to run the docs translator. Optional add-on gated by the RADAR_ENABLED feature flag (default off — a feature flag toggled via Settings/DB, not an env var; see [docs/frameworks/RADAR.md](../frameworks/RADAR.md#flag-radar_enabled-default-off)). -Both variables below are optional overrides used only to point the client at a -self-hosted or forked feed instead of the default OmniRoute Radar feed. See -[docs/frameworks/RADAR.md](../frameworks/RADAR.md) for the full module doc. +The four variables below are optional overrides used only to point the client at a +self-hosted or forked feed / supporter-key flow instead of the default OmniRoute +Radar service. See [docs/frameworks/RADAR.md](../frameworks/RADAR.md) for the full +module doc. -| Variable | Default | Source File | Description | -| -------------------- | ------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | -| `RADAR_FEED_URL` | `https://radar.omniroute.online` | `src/lib/radar/sync.ts` | Base URL of the Radar feed service. Override to point at a self-hosted or forked feed. | -| `RADAR_FEED_PUBKEY` | _(pinned default key)_ | `src/lib/radar/pinnedKeys.ts` | Ed25519 public key (base64-DER SPKI or PEM) used to verify feed signatures from a custom feed. | +| Variable | Default | Source File | Description | +| -------------------------------- | --------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------ | +| `RADAR_FEED_URL` | `https://radar.omniroute.online` | `src/lib/radar/sync.ts` | Base URL of the Radar feed service. Override to point at a self-hosted or forked feed. | +| `RADAR_FEED_PUBKEY` | _(pinned default key)_ | `src/lib/radar/pinnedKeys.ts` | Ed25519 public key (base64-DER SPKI or PEM) used to verify feed signatures from a custom feed. | +| `RADAR_CONTRIBUTOR_CLAIM_URL` | `https://radar.omniroute.online/auth/github` | `src/lib/radar/links.ts` | URL the "I'm a contributor" dashboard button opens (GitHub OAuth supporter-key claim flow). | +| `RADAR_SUPPORTER_PLANS_URL` | `https://radar.omniroute.online/planos` | `src/lib/radar/links.ts` | URL the "Support the project" dashboard button opens (payment/plans page). | --- diff --git a/src/app/(dashboard)/dashboard/radar/page.tsx b/src/app/(dashboard)/dashboard/radar/page.tsx index bbf01666f3..c30ed0b31b 100644 --- a/src/app/(dashboard)/dashboard/radar/page.tsx +++ b/src/app/(dashboard)/dashboard/radar/page.tsx @@ -125,6 +125,12 @@ export default function RadarPage() { campaigns: [], tier: null, }); + // F4/T7 — "get a supporter key" outbound links, relayed by + // GET /api/radar/settings (server-resolved, see src/lib/radar/links.ts). + // Never hardcoded here: this component must never embed an external URL + // literal (see tests/unit/radar-referrals-page-tab.test.ts). + const [contributorClaimUrl, setContributorClaimUrl] = useState(null); + const [supporterPlansUrl, setSupporterPlansUrl] = useState(null); // Fetch catalog const fetchCatalog = useCallback(async () => { @@ -183,6 +189,14 @@ export default function RadarPage() { if (!settingsRes.ok) throw new Error(`HTTP ${settingsRes.status}`); const settingsData = await settingsRes.json(); setOptIn(settingsData.optIn === true); + // F4/T7 — best-effort: keep whatever we already had if the field is + // absent (older cached response shape), never fall back to a literal. + if (typeof settingsData.contributorClaimUrl === "string") { + setContributorClaimUrl(settingsData.contributorClaimUrl); + } + if (typeof settingsData.supporterPlansUrl === "string") { + setSupporterPlansUrl(settingsData.supporterPlansUrl); + } if (settingsData.optIn === true) { // Already opted in — load the catalog now so the populated/empty @@ -352,6 +366,35 @@ export default function RadarPage() { > {activating ? t("activating") : t("activateButton")} + + {/* F4/T7 — "get a supporter key" outbound links. Both open in a + new tab; neither one carries a price/value (D14 — the + only place pricing lives is the destination page). */} + {contributorClaimUrl && supporterPlansUrl && ( +
+

{t("claimSectionTitle")}

+ +

{t("contributorHint")}

+

{t("supporterHint")}

+
+ )} )} diff --git a/src/app/api/radar/settings/route.ts b/src/app/api/radar/settings/route.ts index 9cd4604f23..2b92e08bdb 100644 --- a/src/app/api/radar/settings/route.ts +++ b/src/app/api/radar/settings/route.ts @@ -3,6 +3,13 @@ * snapshot. Powers the dashboard page's "am I already opted in?" check so * a reload doesn't re-show the activation screen (see FIX 3). * + * Also relays the two F4/T7 "get a supporter key" outbound links + * (`contributorClaimUrl`, `supporterPlansUrl` — see `@/lib/radar/links`) so + * the client component never reads `process.env` itself. Smallest surface + * per spec: no dedicated route, reuses this one. Both are plain public + * URLs (no secret, no pricing) — safe to expose alongside the settings + * snapshot, gated by the same flag/auth checks below. + * * POST /api/radar/settings — set Radar opt-in and/or supporter key. * * Zod-validated body: { optIn?: boolean, supporterKey?: string|null } @@ -22,6 +29,7 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { setRadarOptIn, setRadarKey, getRadarSettings } from "@/lib/db/radar"; +import { getContributorClaimUrl, getSupporterPlansUrl } from "@/lib/radar/links"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; export const dynamic = "force-dynamic"; @@ -75,6 +83,8 @@ export async function GET(request: Request) { optIn: settings.optIn, hasSupporterKey: settings.supporterKey !== null, supporterKeyMasked: maskKey(settings.supporterKey), + contributorClaimUrl: getContributorClaimUrl(), + supporterPlansUrl: getSupporterPlansUrl(), }, { headers: { ...CORS_HEADERS, "Cache-Control": "no-store" } }, ); diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index f29e15e90b..3b5e057b25 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "تتم جميع المعالجة على مثيل OmniRoute الخاص بك", "activateButton": "تفعيل", "activating": "جارٍ التفعيل...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "المزود", "colModel": "النموذج", "colQuota": "الحصة", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 642ee7f34a..88df76e148 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Bütün emal sizin OmniRoute instansiyanızda baş verir", "activateButton": "Aktivləşdir", "activating": "Aktivləşdirilir...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Təchizatçı", "colModel": "Model", "colQuota": "Kvota", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index b4c3504cb3..39f618f59a 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Всички обработки се извършват на вашия OmniRoute инстанс", "activateButton": "Активирайте", "activating": "Активиране...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Доставчик", "colModel": "Модел", "colQuota": "Квота", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 4f76394a5d..530aeb48bd 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "সমস্ত প্রক্রিয়াকরণ আপনার OmniRoute ইনস্ট্যান্সে ঘটে", "activateButton": "সক্রিয় করুন", "activating": "সক্রিয় হচ্ছে...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "প্রদানকারী", "colModel": "মডেল", "colQuota": "কোটা", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 16dc33b22d..2d3a48084d 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Veškeré zpracování probíhá na vaší instanci OmniRoute", "activateButton": "Aktivovat", "activating": "Aktivace...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Poskytovatel", "colModel": "Model", "colQuota": "Kvóta", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 54bad0890f..1916bd4fbf 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Al behandling sker på din OmniRoute instans", "activateButton": "Aktiver", "activating": "Aktiverer...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Udbyder", "colModel": "Model", "colQuota": "Kvote", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 22c30f8b72..8a50bbfa89 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Alle Verarbeitungen erfolgen auf Ihrer OmniRoute-Instanz", "activateButton": "Aktivieren", "activating": "Aktivierung...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Anbieter", "colModel": "Modell", "colQuota": "Quote", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 576fe16843..d1bc2c3c5f 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -12261,6 +12261,11 @@ "privacyLocalOnly": "All processing happens on your OmniRoute instance", "activateButton": "Activate", "activating": "Activating...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Provider", "colModel": "Model", "colQuota": "Quota", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 882a3cf4c5..1d3e1829d3 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Todo el procesamiento ocurre en tu instancia de OmniRoute", "activateButton": "Activar", "activating": "Activando...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Proveedor", "colModel": "Modelo", "colQuota": "Cuota", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 47771dc15f..541b4e8201 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "تمام پردازش‌ها در نمونه OmniRoute شما انجام می‌شود", "activateButton": "فعال‌سازی", "activating": "در حال فعال‌سازی...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "تأمین‌کننده", "colModel": "مدل", "colQuota": "سهمیه", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 7703a50a03..681edce5c2 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Kaikki käsittely tapahtuu OmniRoute-instanssissasi", "activateButton": "Aktivoi", "activating": "Aktivointi...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Palveluntarjoaja", "colModel": "Malli", "colQuota": "Kiintiö", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index a58e9539c7..6febef7cbd 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -12237,6 +12237,11 @@ "privacyLocalOnly": "Tout le traitement se fait sur votre instance OmniRoute", "activateButton": "Activer", "activating": "Activation en cours...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Fournisseur", "colModel": "Modèle", "colQuota": "Quota", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index ec5f103ece..bfd5e86155 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "તમામ પ્રક્રિયા તમારા ઓમ્નીરૂટ ઇન્સ્ટન્સ પર થાય છે", "activateButton": "સક્રિય કરો", "activating": "સક્રિય થઈ રહ્યું છે...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "પ્રદાતા", "colModel": "મોડલ", "colQuota": "ક્વોટા", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 0a1acf9c61..138560d266 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "כל העיבוד מתבצע על מופע OmniRoute שלך", "activateButton": "הפעל", "activating": "מפעיל...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "ספק", "colModel": "מודל", "colQuota": "מכסה", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 5d04366372..c1edee4b31 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "सभी प्रोसेसिंग आपके OmniRoute उदाहरण पर होती है", "activateButton": "सक्रिय करें", "activating": "सक्रिय किया जा रहा है...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "प्रदाता", "colModel": "मॉडल", "colQuota": "कोटा", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index b44bd46aa2..93da62a51e 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Minden feldolgozás a te OmniRoute példányodon történik", "activateButton": "Aktiválás", "activating": "Aktiválás...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Szolgáltató", "colModel": "Modell", "colQuota": "Kvóta", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 8c75baac46..cdd2643680 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Semua pemrosesan terjadi di instance OmniRoute Anda", "activateButton": "Aktifkan", "activating": "Mengaktifkan...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Penyedia", "colModel": "Model", "colQuota": "Kuota", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 37148788eb..682cfbe3c8 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "सभी प्रोसेसिंग आपके OmniRoute इंस्टेंस पर होती है", "activateButton": "सक्रिय करें", "activating": "सक्रियण हो रहा है...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "प्रदाता", "colModel": "मॉडल", "colQuota": "कोटा", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 82049c41da..db40130bfb 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Tutto l'elaborazione avviene sulla tua istanza OmniRoute", "activateButton": "Attiva", "activating": "Attivazione in corso...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Fornitore", "colModel": "Modello", "colQuota": "Quota", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index b8dbb913a3..a9e11214a4 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "すべての処理はあなたのOmniRouteインスタンスで行われます", "activateButton": "アクティブにする", "activating": "アクティブにしています...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "プロバイダー", "colModel": "モデル", "colQuota": "クォータ", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 604cb2525f..5629f8fe31 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "모든 처리는 귀하의 OmniRoute 인스턴스에서 발생합니다", "activateButton": "활성화", "activating": "활성화 중...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "제공자", "colModel": "모델", "colQuota": "할당량", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index f6a923ae9b..8a566c0949 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "सर्व प्रक्रिया तुमच्या OmniRoute उदाहरणावर होते", "activateButton": "सक्रिय करा", "activating": "सक्रिय करत आहे...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "प्रदाता", "colModel": "मॉडेल", "colQuota": "कोटा", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index d59a41387b..5bc9cefc53 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Semua pemprosesan berlaku pada instance OmniRoute anda", "activateButton": "Aktifkan", "activating": "Mengaktifkan...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Penyedia", "colModel": "Model", "colQuota": "Kuota", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index b30b2e2f59..74be8313b2 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Alle verwerking gebeurt op uw OmniRoute-instantie", "activateButton": "Activeren", "activating": "Activeren...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Leverancier", "colModel": "Model", "colQuota": "Quota", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 8ed665b57d..99d6f2b39c 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "All behandling skjer på din OmniRoute-instans", "activateButton": "Aktiver", "activating": "Aktiverer...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Leverandør", "colModel": "Modell", "colQuota": "Kvote", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index b82c979cf3..52e88e96b8 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "All processing happens on your OmniRoute instance", "activateButton": "Activate", "activating": "Activating...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Provider", "colModel": "Model", "colQuota": "Quota", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index b38c51280b..919f1ea176 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -12234,6 +12234,11 @@ "privacyLocalOnly": "Wszystkie przetwarzanie odbywa się na twojej instancji OmniRoute", "activateButton": "Aktywuj", "activating": "Aktywacja...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Dostawca", "colModel": "Model", "colQuota": "Kwota", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index bff818e63b..8ab11f7702 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -12261,6 +12261,11 @@ "privacyLocalOnly": "Todo processamento acontece na sua instância OmniRoute", "activateButton": "Ativar", "activating": "Ativando...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Provedor", "colModel": "Modelo", "colQuota": "Cota", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index ba06cc71f6..a8433114e9 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Todo o processamento ocorre na sua instância OmniRoute", "activateButton": "Ativar", "activating": "A ativar...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Fornecedor", "colModel": "Modelo", "colQuota": "Quota", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 646b9e12af..8ffc2b5faa 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Toate procesările au loc pe instanța ta OmniRoute", "activateButton": "Activează", "activating": "Activare...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Furnizor", "colModel": "Model", "colQuota": "Cotă", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index a44acd37c7..d2d37004f1 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -12306,6 +12306,11 @@ "privacyLocalOnly": "Все обработки происходят на вашем экземпляре OmniRoute", "activateButton": "Активировать", "activating": "Активация...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Провайдер", "colModel": "Модель", "colQuota": "Квота", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 269c5a5972..f27b22a0c6 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Všetko spracovanie prebieha na vašej inštancii OmniRoute", "activateButton": "Aktivovať", "activating": "Aktivujem...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Poskytovateľ", "colModel": "Model", "colQuota": "Kvóta", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 325a77b4b9..1bebb2b029 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "All bearbetning sker på din OmniRoute-instans", "activateButton": "Aktivera", "activating": "Aktiverar...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Leverantör", "colModel": "Modell", "colQuota": "Kvot", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 216476a557..134aaea826 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "All bearbetning sker på din OmniRoute-instans", "activateButton": "Aktivera", "activating": "Aktiverar...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Leverantör", "colModel": "Modell", "colQuota": "Kvot", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 36be7747a8..101961d4f7 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "அனைத்து செயலாக்கமும் உங்கள் OmniRoute instance இல் நடைபெறும்", "activateButton": "செயல்படுத்தவும்", "activating": "செயல்படுத்துகிறது...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "வழங்குநர்", "colModel": "மாதிரி", "colQuota": "கோட்டை", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index 174cccb1a4..d6fb26ec7b 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "అన్ని ప్రాసెసింగ్ మీ OmniRoute ఉదాహరణపై జరుగుతుంది", "activateButton": "యాక్టివేట్ చేయండి", "activating": "యాక్టివేట్ అవుతోంది...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "ప్రొవైడర్", "colModel": "మోడల్", "colQuota": "క్వోటా", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 00b0d6ad6b..1c2da07af9 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "การประมวลผลทั้งหมดเกิดขึ้นบนอินสแตนซ์ OmniRoute ของคุณ", "activateButton": "เปิดใช้งาน", "activating": "กำลังเปิดใช้งาน...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "ผู้ให้บริการ", "colModel": "โมเดล", "colQuota": "โควต้า", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 8371e20be9..c8f85cf4b1 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Tüm işleme, OmniRoute örneğinizde gerçekleşir", "activateButton": "Etkinleştir", "activating": "Etkinleştiriliyor...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Sağlayıcı", "colModel": "Model", "colQuota": "Kota", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index fb3e7b80df..edf9a00c13 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "Усе оброблення відбувається на вашій інстанції OmniRoute", "activateButton": "Активувати", "activating": "Активація...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Постачальник", "colModel": "Модель", "colQuota": "Квота", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 6cd1caf669..bfe3fce3bf 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "تمام پروسیسنگ آپ کے OmniRoute انسٹنس پر ہوتی ہے", "activateButton": "چالو کریں", "activating": "چالو ہو رہا ہے...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "فراہم کنندہ", "colModel": "ماڈل", "colQuota": "کوٹہ", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 3672a1733f..3951d02b2e 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -12261,6 +12261,11 @@ "privacyLocalOnly": "Tất cả xử lý diễn ra trên phiên bản OmniRoute của bạn", "activateButton": "Kích hoạt", "activating": "Đang kích hoạt...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "Nhà cung cấp", "colModel": "Mô hình", "colQuota": "Hạn ngạch", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 58d53bdacd..d62d9a0a57 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "所有处理都在您的OmniRoute实例上进行", "activateButton": "激活", "activating": "正在激活...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "提供者", "colModel": "模型", "colQuota": "配额", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 9c08c95552..0e130048f7 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -12212,6 +12212,11 @@ "privacyLocalOnly": "所有處理都在您的 OmniRoute 實例上進行", "activateButton": "啟用", "activating": "正在啟用...", + "claimSectionTitle": "Don't have a supporter key yet?", + "contributorButton": "I'm a contributor", + "contributorHint": "Verify your GitHub account — 5+ merged pull requests or a top-100 contributor spot unlocks a free supporter key.", + "supporterButton": "Support the project", + "supporterHint": "Support OmniRoute to get a supporter key with the live catalog and extra perks.", "colProvider": "提供者", "colModel": "模型", "colQuota": "配額", diff --git a/src/lib/radar/links.ts b/src/lib/radar/links.ts new file mode 100644 index 0000000000..3641efa763 --- /dev/null +++ b/src/lib/radar/links.ts @@ -0,0 +1,42 @@ +/** + * links.ts — pure config for the two Radar "get a supporter key" outbound + * links (F4/T7): the contributor-claim (GitHub OAuth) flow and the + * supporter-plans (payment) page on the private radar.omniroute.online + * server. + * + * DELIBERATELY DB-FREE and side-effect-free — same shape as the + * `RADAR_FEED_URL` override already used by `./sync.ts`, so forks/self-hosters + * point both links at their own deployment via env vars (see + * docs/frameworks/RADAR.md). + * + * These functions are read server-side only (inside a route handler) and the + * resolved URLs are relayed to the client via GET /api/radar/settings — the + * dashboard page never reads `process.env` itself, matching the pattern the + * D28 referral links already established for the private feed. + * + * No price or monetary value is ever resolved, stored, or exposed here — the + * URLs point at pages that are themselves the ONLY place pricing lives + * (spec D14: no pricing in the OSS repo). + */ + +/** Default contributor-claim entry point — starts the GitHub OAuth flow. */ +const DEFAULT_CONTRIBUTOR_CLAIM_URL = "https://radar.omniroute.online/auth/github"; + +/** Default supporter plans/payment page. */ +const DEFAULT_SUPPORTER_PLANS_URL = "https://radar.omniroute.online/planos"; + +/** + * URL that starts the "I'm a contributor" GitHub OAuth claim flow. + * Override with `RADAR_CONTRIBUTOR_CLAIM_URL` for forks/self-hosters. + */ +export function getContributorClaimUrl(): string { + return process.env.RADAR_CONTRIBUTOR_CLAIM_URL || DEFAULT_CONTRIBUTOR_CLAIM_URL; +} + +/** + * URL for the "Support the project" plans/payment page. + * Override with `RADAR_SUPPORTER_PLANS_URL` for forks/self-hosters. + */ +export function getSupporterPlansUrl(): string { + return process.env.RADAR_SUPPORTER_PLANS_URL || DEFAULT_SUPPORTER_PLANS_URL; +} diff --git a/tests/unit/radar-api-routes.test.ts b/tests/unit/radar-api-routes.test.ts index 395bc17118..7d10e4351b 100644 --- a/tests/unit/radar-api-routes.test.ts +++ b/tests/unit/radar-api-routes.test.ts @@ -355,6 +355,7 @@ test("POST /api/radar/sync: authenticated, invalid body => 400", async () => { // --------------------------------------------------------------------------- // FIX 3 — GET /api/radar/settings: { optIn, hasSupporterKey, supporterKeyMasked } +// F4/T7 — same response also relays contributorClaimUrl/supporterPlansUrl. // --------------------------------------------------------------------------- test("GET /api/radar/settings: flag on, authenticated, default state => optIn false, no key", async () => { @@ -371,6 +372,9 @@ test("GET /api/radar/settings: flag on, authenticated, default state => optIn fa assert.equal(body.optIn, false); assert.equal(body.hasSupporterKey, false); assert.equal(body.supporterKeyMasked, null); + // F4/T7: default claim/plans links are always present, opt-in or not. + assert.equal(body.contributorClaimUrl, "https://radar.omniroute.online/auth/github"); + assert.equal(body.supporterPlansUrl, "https://radar.omniroute.online/planos"); }); test("GET /api/radar/settings: flag on, authenticated, after opt-in + key => reflects persisted state, never raw key", async () => { @@ -402,6 +406,28 @@ test("GET /api/radar/settings: flag on, authenticated, after opt-in + key => ref assert.ok(!text.includes(RAW_KEY), "raw key must NEVER appear in the serialized response body"); }); +test("GET /api/radar/settings: F4/T7 claim/plans links honor env overrides (fork-friendly)", async () => { + resetStorage(); + process.env.RADAR_ENABLED = "true"; + process.env.RADAR_CONTRIBUTOR_CLAIM_URL = "https://fork.example.com/auth/github"; + process.env.RADAR_SUPPORTER_PLANS_URL = "https://fork.example.com/plans"; + + try { + const { GET } = await import("../../src/app/api/radar/settings/route.ts"); + const response = await GET( + mockGetRequest("http://localhost:20128/api/radar/settings", await authHeaders()), + ); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.equal(body.contributorClaimUrl, "https://fork.example.com/auth/github"); + assert.equal(body.supporterPlansUrl, "https://fork.example.com/plans"); + } finally { + delete process.env.RADAR_CONTRIBUTOR_CLAIM_URL; + delete process.env.RADAR_SUPPORTER_PLANS_URL; + } +}); + // --------------------------------------------------------------------------- // Tests: error sanitization (Hard Rule #12) // --------------------------------------------------------------------------- diff --git a/tests/unit/radar-claim-buttons.test.ts b/tests/unit/radar-claim-buttons.test.ts new file mode 100644 index 0000000000..240e0670b5 --- /dev/null +++ b/tests/unit/radar-claim-buttons.test.ts @@ -0,0 +1,122 @@ +/** + * tests/unit/radar-claim-buttons.test.ts + * + * TDD guard for the F4/T7 "get a supporter key" buttons on the Radar + * activation screen (src/app/(dashboard)/dashboard/radar/page.tsx): + * + * - "I'm a contributor" and "Support the project" open in a new tab + * (target="_blank" rel="noopener noreferrer") and never hardcode an + * external URL — both links come from GET /api/radar/settings + * (server-resolved via src/lib/radar/links.ts), never process.env + * read client-side. + * - No price/monetary value appears anywhere in the page source (D14). + * - Every new t("...") key referenced exists (non-empty) in en.json and + * all locale files. + * + * Structural, source-based — same style as + * tests/unit/radar-referrals-page-tab.test.ts — deliberately avoids a full + * component render (no jsdom harness in this repo's unit runner). + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +const PAGE_PATH = path.resolve( + process.cwd(), + "src/app/(dashboard)/dashboard/radar/page.tsx" +); +const PAGE_SRC = fs.readFileSync(PAGE_PATH, "utf-8"); + +const NEW_KEYS = [ + "claimSectionTitle", + "contributorButton", + "contributorHint", + "supporterButton", + "supporterHint", +]; + +test("radar page: claim/plans links are state, never a hardcoded external URL literal", () => { + assert.ok( + PAGE_SRC.includes("contributorClaimUrl") && PAGE_SRC.includes("supporterPlansUrl"), + "page must reference contributorClaimUrl/supporterPlansUrl state" + ); + // Same guard as the D28 referrals test: no literal https:// (except in + // comments) anywhere in this client component — links are always + // server-resolved and relayed through the settings fetch. + assert.ok( + !/https?:\/\/(?!localhost)/.test(PAGE_SRC.replace(/\/\*[\s\S]*?\*\//g, "")), + "page must never hardcode an external URL directly" + ); + // Never read process.env directly in this client component. + assert.ok( + !PAGE_SRC.includes("process.env"), + "page must never read process.env client-side — URLs come from the settings fetch" + ); +}); + +test("radar page: both buttons open in a new tab safely", () => { + const contributorAnchor = PAGE_SRC.match( + /href=\{contributorClaimUrl\}[\s\S]{0,120}/ + )?.[0]; + const supporterAnchor = PAGE_SRC.match(/href=\{supporterPlansUrl\}[\s\S]{0,120}/)?.[0]; + assert.ok(contributorAnchor, "contributorClaimUrl anchor must exist"); + assert.ok(supporterAnchor, "supporterPlansUrl anchor must exist"); + for (const anchor of [contributorAnchor, supporterAnchor]) { + assert.ok(anchor!.includes('target="_blank"'), "must open in a new tab"); + assert.ok( + anchor!.includes('rel="noopener noreferrer"'), + "must set rel=noopener noreferrer" + ); + } +}); + +test("radar page: references the 5 new claim-section t(...) keys", () => { + for (const key of NEW_KEYS) { + assert.ok( + PAGE_SRC.includes(`t("${key}")`), + `page.tsx must reference t("${key}")` + ); + } +}); + +test("radar page + all 43 locale files: no price/monetary value in the claim section copy (D14)", () => { + // D14: no pricing anywhere in the OSS repo, only a link to the plans page. + const PRICE_PATTERN = /\$\s?\d|R\$\s?\d|\d+[.,]\d{2}\s?(USD|BRL|EUR)|\b(lifetime|life-time)\b.{0,20}\$/i; + assert.ok(!PRICE_PATTERN.test(PAGE_SRC), "page.tsx must not contain a price/monetary value"); + + const messagesDir = path.resolve(process.cwd(), "src/i18n/messages"); + const files = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json")); + assert.ok(files.length >= 40, `expected ~43 locale files, found ${files.length}`); + + for (const file of files) { + const data = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf-8")); + const radarPage = data.radarPage as Record | undefined; + assert.ok(radarPage, `${file}: missing radarPage namespace`); + for (const key of NEW_KEYS) { + const value = radarPage![key]; + assert.equal(typeof value, "string", `${file}: radarPage.${key} must be a string`); + assert.ok((value as string).length > 0, `${file}: radarPage.${key} is empty`); + assert.ok( + !PRICE_PATTERN.test(value as string), + `${file}: radarPage.${key} must not contain a price/monetary value` + ); + } + } +}); + +test("no OSS file mentions the word 'freellmapi'", () => { + // Repo-wide guard scoped to the files this task touches — the full + // repo-wide ban is enforced elsewhere; this is a local regression check + // for the files this feature added/edited. + const filesToCheck = [ + PAGE_PATH, + path.resolve(process.cwd(), "src/lib/radar/links.ts"), + path.resolve(process.cwd(), "src/app/api/radar/settings/route.ts"), + ]; + for (const file of filesToCheck) { + const src = fs.readFileSync(file, "utf-8"); + assert.ok(!/freellmapi/i.test(src), `${file} must not mention freellmapi`); + } +}); diff --git a/tests/unit/radar-links.test.ts b/tests/unit/radar-links.test.ts new file mode 100644 index 0000000000..9a173ec89e --- /dev/null +++ b/tests/unit/radar-links.test.ts @@ -0,0 +1,56 @@ +/** + * tests/unit/radar-links.test.ts + * + * TDD guard for src/lib/radar/links.ts — the two outbound "get a supporter + * key" links (F4/T7): contributor-claim (GitHub OAuth) and supporter-plans + * (payment page). Pure, DB-free module: defaults + env override only. + * + * No price/monetary value assertion lives here on purpose — this module + * never resolves one (D14: pricing only lives on the private plans page the + * URL points at, never in the OSS repo). + */ + +import test from "node:test"; +import assert from "node:assert/strict"; + +test.beforeEach(() => { + delete process.env.RADAR_CONTRIBUTOR_CLAIM_URL; + delete process.env.RADAR_SUPPORTER_PLANS_URL; +}); + +test.after(() => { + delete process.env.RADAR_CONTRIBUTOR_CLAIM_URL; + delete process.env.RADAR_SUPPORTER_PLANS_URL; +}); + +test("getContributorClaimUrl: defaults to the radar.omniroute.online GitHub OAuth entry point", async () => { + const { getContributorClaimUrl } = await import("../../src/lib/radar/links.ts"); + assert.equal(getContributorClaimUrl(), "https://radar.omniroute.online/auth/github"); +}); + +test("getContributorClaimUrl: honors RADAR_CONTRIBUTOR_CLAIM_URL override", async () => { + process.env.RADAR_CONTRIBUTOR_CLAIM_URL = "https://fork.example.com/auth/github"; + const { getContributorClaimUrl } = await import("../../src/lib/radar/links.ts"); + assert.equal(getContributorClaimUrl(), "https://fork.example.com/auth/github"); +}); + +test("getSupporterPlansUrl: defaults to the radar.omniroute.online plans page", async () => { + const { getSupporterPlansUrl } = await import("../../src/lib/radar/links.ts"); + assert.equal(getSupporterPlansUrl(), "https://radar.omniroute.online/planos"); +}); + +test("getSupporterPlansUrl: honors RADAR_SUPPORTER_PLANS_URL override", async () => { + process.env.RADAR_SUPPORTER_PLANS_URL = "https://fork.example.com/plans"; + const { getSupporterPlansUrl } = await import("../../src/lib/radar/links.ts"); + assert.equal(getSupporterPlansUrl(), "https://fork.example.com/plans"); +}); + +test("getContributorClaimUrl / getSupporterPlansUrl: empty-string env falls back to default (not a blank link)", async () => { + process.env.RADAR_CONTRIBUTOR_CLAIM_URL = ""; + process.env.RADAR_SUPPORTER_PLANS_URL = ""; + const { getContributorClaimUrl, getSupporterPlansUrl } = await import( + "../../src/lib/radar/links.ts" + ); + assert.equal(getContributorClaimUrl(), "https://radar.omniroute.online/auth/github"); + assert.equal(getSupporterPlansUrl(), "https://radar.omniroute.online/planos"); +});