Repository navigation
feat(dashboard): i18n the proxy settings UI #3310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,60 +1,62 @@ | ||
| "use client"; | ||
| import { useTranslations } from "next-intl"; | ||
| import { Card } from "@/shared/components"; | ||
|
|
||
| export default function DocumentationTab() { | ||
| const t = useTranslations("settings"); | ||
|
|
||
| return ( | ||
| <Card className="p-6 space-y-6"> | ||
| <section> | ||
| <h3 className="font-semibold mb-2">Proxy scope resolution</h3> | ||
| <h3 className="font-semibold mb-2">{t("proxyDocumentationScopeTitle")}</h3> | ||
| <p className="text-sm text-text-muted"> | ||
| OmniRoute resolves the outbound proxy in priority order: | ||
| <strong> combo → account → provider → global</strong>. The most specific scope wins. | ||
| {t("proxyDocumentationScopeDescBefore")} | ||
| <strong>{t("proxyDocumentationScopeOrder")}</strong> | ||
| {t("proxyDocumentationScopeDescAfter")} | ||
| </p> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 className="font-semibold mb-2">Adding a custom proxy</h3> | ||
| <h3 className="font-semibold mb-2">{t("proxyDocumentationAddTitle")}</h3> | ||
| <p className="text-sm text-text-muted"> | ||
| Go to the <strong>Proxy Pool</strong> tab → click <em>+ Add proxy</em>. Fill in type | ||
| (http/https/socks5), host, and port. Optionally assign it to a scope. | ||
| {t("proxyDocumentationAddDescBefore")} | ||
| <strong>{t("proxyPoolTab")}</strong> | ||
| {t("proxyDocumentationAddDescMiddle")} | ||
| <em>{t("proxyDocumentationAddCta")}</em> | ||
| {t("proxyDocumentationAddDescAfter")} | ||
| </p> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 className="font-semibold mb-2">Bulk import format</h3> | ||
| <h3 className="font-semibold mb-2">{t("proxyDocumentationBulkTitle")}</h3> | ||
| <pre className="text-xs bg-surface-alt/50 p-3 rounded mt-1 overflow-x-auto"> | ||
| {`http://user:pass@1.2.3.4:8080\nhttps://5.6.7.8:3128\nsocks5://9.0.1.2:1080`} | ||
| </pre> | ||
| <p className="text-sm text-text-muted mt-1"> | ||
| Pipe-delimited fields: <code>type|host|port|user|pass|name</code> | ||
| {t("proxyDocumentationBulkDesc")} <code>type|host|port|user|pass|name</code> | ||
| </p> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 className="font-semibold mb-2">SOCKS5</h3> | ||
| <p className="text-sm text-text-muted"> | ||
| SOCKS5 proxies are disabled by default. Set{" "} | ||
| {t("proxyDocumentationSocks5DescBefore")}{" "} | ||
| <code className="bg-surface-alt px-1 rounded">ENABLE_SOCKS5_PROXY=true</code> to enable. | ||
|
Comment on lines
+43
to
44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The string For example, in In |
||
| </p> | ||
|
Comment on lines
+43
to
45
|
||
| </section> | ||
|
|
||
| <section> | ||
| <h3 className="font-semibold mb-2">Free Pool</h3> | ||
| <h3 className="font-semibold mb-2">{t("freePoolTab")}</h3> | ||
| <p className="text-sm text-text-muted"> | ||
| The Free Pool tab aggregates proxies from 1proxy, Proxifly, and IPLocate. Use ⊕ to test | ||
| and promote a proxy to your registry. Only proxies that pass the connectivity test are | ||
| added. | ||
| {t("proxyDocumentationFreePoolDesc")} | ||
| </p> | ||
| </section> | ||
|
|
||
| <section> | ||
| <h3 className="font-semibold mb-2">Vercel Relay</h3> | ||
| <p className="text-sm text-text-muted"> | ||
| The Vercel Relay is an outbound edge relay — not an inbound tunnel. Deploying one sends | ||
| LLM API calls through Vercel's dynamic IPs, bypassing datacenter geo-blocks and rate | ||
| limits. The relay is protected by a generated secret header ( | ||
| <code className="bg-surface-alt px-1 rounded">x-relay-auth</code>). Your Vercel token is | ||
| used only during deploy and is never stored. | ||
| {t("proxyDocumentationVercelRelayDescBefore")} ( | ||
| <code className="bg-surface-alt px-1 rounded">x-relay-auth</code>). {t("proxyDocumentationVercelRelayDescAfter")} | ||
| </p> | ||
| </section> | ||
| </Card> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||
| "use client"; | ||||||
| import { useState, useEffect, useCallback } from "react"; | ||||||
| import { useTranslations } from "next-intl"; | ||||||
| import { Button } from "@/shared/components"; | ||||||
| import SourceToggleBar, { | ||||||
| type SourceId, | ||||||
|
|
@@ -17,6 +18,7 @@ type FreePoolStats = { | |||||
| }; | ||||||
|
|
||||||
| export default function FreePoolTab() { | ||||||
| const t = useTranslations("settings"); | ||||||
| const [proxies, setProxies] = useState<FreeProxyRowData[]>([]); | ||||||
| const [stats, setStats] = useState<FreePoolStats | null>(null); | ||||||
| const [disabledSources, setDisabledSources] = useState<Set<SourceId>>(new Set()); | ||||||
|
|
@@ -148,9 +150,9 @@ export default function FreePoolTab() { | |||||
| value={filterProtocol} | ||||||
| onChange={(e) => setFilterProtocol(e.target.value)} | ||||||
| className="text-xs bg-surface-alt border border-border rounded px-2 py-1" | ||||||
| aria-label="Filter by protocol" | ||||||
| aria-label={t("proxyFreePoolFilterProtocol")} | ||||||
| > | ||||||
| <option value="">Protocol</option> | ||||||
| <option value="">{t("proxyFreePoolProtocol")}</option> | ||||||
| {["http", "https", "socks4", "socks5"].map((p) => ( | ||||||
| <option key={p} value={p}> | ||||||
| {p.toUpperCase()} | ||||||
|
|
@@ -159,44 +161,44 @@ export default function FreePoolTab() { | |||||
| </select> | ||||||
| <input | ||||||
| type="text" | ||||||
| placeholder="Country (e.g. US)" | ||||||
| placeholder={t("proxyFreePoolCountryPlaceholder")} | ||||||
| value={filterCountry} | ||||||
| onChange={(e) => setFilterCountry(e.target.value.toUpperCase().slice(0, 2))} | ||||||
| className="text-xs bg-surface-alt border border-border rounded px-2 py-1 w-28" | ||||||
| aria-label="Filter by country" | ||||||
| aria-label={t("proxyFreePoolFilterCountry")} | ||||||
| /> | ||||||
| <input | ||||||
| type="number" | ||||||
| placeholder="Min quality" | ||||||
| placeholder={t("proxyFreePoolMinQualityPlaceholder")} | ||||||
| value={minQuality} | ||||||
| onChange={(e) => setMinQuality(e.target.value)} | ||||||
| min={0} | ||||||
| max={100} | ||||||
| className="text-xs bg-surface-alt border border-border rounded px-2 py-1 w-24" | ||||||
| aria-label="Minimum quality score" | ||||||
| aria-label={t("proxyFreePoolMinQualityLabel")} | ||||||
| /> | ||||||
| <Button size="sm" variant="secondary" icon="sync" onClick={handleSync} disabled={syncing}> | ||||||
| {syncing ? "Syncing..." : "Sync All"} | ||||||
| {syncing ? t("syncing") : t("proxyFreePoolSyncAll")} | ||||||
| </Button> | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| {stats && ( | ||||||
| <div className="text-xs text-text-muted flex gap-4 flex-wrap"> | ||||||
| <span>Total: {stats.total}</span> | ||||||
| <span>In pool: {stats.inPool}</span> | ||||||
| {stats.avgQuality != null && <span>Avg quality: {stats.avgQuality}</span>} | ||||||
| <span>{t("proxyFreePoolTotal")}: {stats.total}</span> | ||||||
| <span>{t("proxyFreePoolInPool")}: {stats.inPool}</span> | ||||||
| {stats.avgQuality != null && <span>{t("proxyFreePoolAvgQuality")}: {stats.avgQuality}</span>} | ||||||
| {stats.lastSyncAt && ( | ||||||
| <span>Last sync: {new Date(stats.lastSyncAt).toLocaleTimeString()}</span> | ||||||
| <span>{t("lastSync")}: {new Date(stats.lastSyncAt).toLocaleTimeString()}</span> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using To prevent this, consider adding
Suggested change
|
||||||
| )} | ||||||
| </div> | ||||||
| )} | ||||||
|
|
||||||
| {selected.size > 0 && ( | ||||||
| <div className="flex items-center gap-2 p-2 bg-primary/10 rounded border border-primary/20"> | ||||||
| <span className="text-xs">{selected.size} selected</span> | ||||||
| <span className="text-xs">{t("proxyFreePoolSelected", { count: selected.size })}</span> | ||||||
| <Button size="sm" variant="primary" onClick={() => handleBulkAdd(Array.from(selected))}> | ||||||
| ⊕ Add selected to pool | ||||||
| {t("proxyFreePoolAddSelected")} | ||||||
| </Button> | ||||||
| {bulkProgress && <span className="text-xs text-text-muted">{bulkProgress}</span>} | ||||||
| </div> | ||||||
|
|
@@ -209,7 +211,7 @@ export default function FreePoolTab() { | |||||
| variant="secondary" | ||||||
| onClick={() => handleBulkAdd(notInPoolProxies.slice(0, 100).map((p) => p.id))} | ||||||
| > | ||||||
| ⊕ Add all visible to pool | ||||||
| {t("proxyFreePoolAddVisible")} | ||||||
| </Button> | ||||||
| </div> | ||||||
| )} | ||||||
|
|
@@ -220,22 +222,22 @@ export default function FreePoolTab() { | |||||
| <tr> | ||||||
| <th className="px-3 py-2 text-left w-8" scope="col"></th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Source | ||||||
| {t("proxyFreePoolSource")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Host:Port | ||||||
| {t("proxyFreePoolHostPort")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Type | ||||||
| {t("proxyFreePoolType")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Country | ||||||
| {t("proxyFreePoolCountry")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Quality | ||||||
| {t("proxyFreePoolQuality")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"> | ||||||
| Latency | ||||||
| {t("proxyFreePoolLatency")} | ||||||
| </th> | ||||||
| <th className="px-3 py-2 text-left" scope="col"></th> | ||||||
| </tr> | ||||||
|
|
@@ -244,13 +246,13 @@ export default function FreePoolTab() { | |||||
| {loading ? ( | ||||||
| <tr> | ||||||
| <td colSpan={8} className="px-3 py-8 text-center text-text-muted"> | ||||||
| Loading... | ||||||
| {t("loading")} | ||||||
| </td> | ||||||
| </tr> | ||||||
| ) : proxies.length === 0 ? ( | ||||||
| <tr> | ||||||
| <td colSpan={8} className="px-3 py-8 text-center text-text-muted"> | ||||||
| No proxies found. Click Sync All to fetch from sources. | ||||||
| {t("proxyFreePoolEmpty")} | ||||||
| </td> | ||||||
| </tr> | ||||||
| ) : ( | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable
tis used here (t("proxySubTabsAria")), but it does not appear to be imported or initialized in this file. Please ensure you importuseTranslationsfromnext-intland initializetat the top of theProxyTabcomponent: