summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/routes
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-27 15:50:50 -0600
committerGitHub <[email protected]>2026-02-27 15:50:50 -0600
commit7f851da15ecde7db6535aae1f13009d3d6068daf (patch)
treee8978e858c09a6507dfe0ad40b32b008d634b82e /packages/console/app/src/routes
parenta3bdb974b36714ace5e2a41e76da8aa6682eb4c8 (diff)
downloadopencode-7f851da15ecde7db6535aae1f13009d3d6068daf.tar.gz
opencode-7f851da15ecde7db6535aae1f13009d3d6068daf.zip
chore(console): i18n sync (#15360)
Diffstat (limited to 'packages/console/app/src/routes')
-rw-r--r--packages/console/app/src/routes/[...404].tsx4
-rw-r--r--packages/console/app/src/routes/api/enterprise.ts11
-rw-r--r--packages/console/app/src/routes/auth/[...callback].ts4
-rw-r--r--packages/console/app/src/routes/bench/submission.ts5
-rw-r--r--packages/console/app/src/routes/brand/index.tsx17
-rw-r--r--packages/console/app/src/routes/download/[channel]/[platform].ts2
-rw-r--r--packages/console/app/src/routes/stripe/webhook.ts2
-rw-r--r--packages/console/app/src/routes/temp.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx3
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx3
-rw-r--r--packages/console/app/src/routes/zen/util/handler.ts74
-rw-r--r--packages/console/app/src/routes/zen/util/rateLimiter.ts9
12 files changed, 89 insertions, 49 deletions
diff --git a/packages/console/app/src/routes/[...404].tsx b/packages/console/app/src/routes/[...404].tsx
index 414491f9d..e20ec36fd 100644
--- a/packages/console/app/src/routes/[...404].tsx
+++ b/packages/console/app/src/routes/[...404].tsx
@@ -16,8 +16,8 @@ export default function NotFound() {
<div data-component="content">
<section data-component="top">
<a href={language.route("/")} data-slot="logo-link">
- <img data-slot="logo light" src={logoLight} alt="opencode logo light" />
- <img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
+ <img data-slot="logo light" src={logoLight} alt={i18n.t("notFound.logoLightAlt")} />
+ <img data-slot="logo dark" src={logoDark} alt={i18n.t("notFound.logoDarkAlt")} />
</a>
<h1 data-slot="title">{i18n.t("notFound.heading")}</h1>
</section>
diff --git a/packages/console/app/src/routes/api/enterprise.ts b/packages/console/app/src/routes/api/enterprise.ts
index 6776a7b3c..27e2dc493 100644
--- a/packages/console/app/src/routes/api/enterprise.ts
+++ b/packages/console/app/src/routes/api/enterprise.ts
@@ -1,5 +1,7 @@
import type { APIEvent } from "@solidjs/start/server"
import { AWS } from "@opencode-ai/console-core/aws.js"
+import { i18n } from "~/i18n"
+import { localeFromRequest } from "~/lib/language"
interface EnterpriseFormData {
name: string
@@ -9,18 +11,19 @@ interface EnterpriseFormData {
}
export async function POST(event: APIEvent) {
+ const dict = i18n(localeFromRequest(event.request))
try {
const body = (await event.request.json()) as EnterpriseFormData
// Validate required fields
if (!body.name || !body.role || !body.email || !body.message) {
- return Response.json({ error: "All fields are required" }, { status: 400 })
+ return Response.json({ error: dict["enterprise.form.error.allFieldsRequired"] }, { status: 400 })
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(body.email)) {
- return Response.json({ error: "Invalid email format" }, { status: 400 })
+ return Response.json({ error: dict["enterprise.form.error.invalidEmailFormat"] }, { status: 400 })
}
// Create email content
@@ -39,9 +42,9 @@ ${body.email}`.trim()
replyTo: body.email,
})
- return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
+ return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
} catch (error) {
console.error("Error processing enterprise form:", error)
- return Response.json({ error: "Internal server error" }, { status: 500 })
+ return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
}
}
diff --git a/packages/console/app/src/routes/auth/[...callback].ts b/packages/console/app/src/routes/auth/[...callback].ts
index 664f6cc6d..00bb89406 100644
--- a/packages/console/app/src/routes/auth/[...callback].ts
+++ b/packages/console/app/src/routes/auth/[...callback].ts
@@ -2,15 +2,17 @@ import { redirect } from "@solidjs/router"
import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
import { useAuthSession } from "~/context/auth"
+import { i18n } from "~/i18n"
import { localeFromRequest, route } from "~/lib/language"
export async function GET(input: APIEvent) {
const url = new URL(input.request.url)
const locale = localeFromRequest(input.request)
+ const dict = i18n(locale)
try {
const code = url.searchParams.get("code")
- if (!code) throw new Error("No code found")
+ if (!code) throw new Error(dict["auth.callback.error.codeMissing"])
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
if (result.err) throw new Error(result.err.message)
const decoded = AuthClient.decode(result.tokens.access, {} as any)
diff --git a/packages/console/app/src/routes/bench/submission.ts b/packages/console/app/src/routes/bench/submission.ts
index 94639439b..969ff1659 100644
--- a/packages/console/app/src/routes/bench/submission.ts
+++ b/packages/console/app/src/routes/bench/submission.ts
@@ -2,6 +2,8 @@ import type { APIEvent } from "@solidjs/start/server"
import { Database } from "@opencode-ai/console-core/drizzle/index.js"
import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js"
import { Identifier } from "@opencode-ai/console-core/identifier.js"
+import { i18n } from "~/i18n"
+import { localeFromRequest } from "~/lib/language"
interface SubmissionBody {
model: string
@@ -10,10 +12,11 @@ interface SubmissionBody {
}
export async function POST(event: APIEvent) {
+ const dict = i18n(localeFromRequest(event.request))
const body = (await event.request.json()) as SubmissionBody
if (!body.model || !body.agent || !body.result) {
- return Response.json({ error: "All fields are required" }, { status: 400 })
+ return Response.json({ error: dict["bench.submission.error.allFieldsRequired"] }, { status: 400 })
}
await Database.use((tx) =>
diff --git a/packages/console/app/src/routes/brand/index.tsx b/packages/console/app/src/routes/brand/index.tsx
index 9140462c7..af89f4985 100644
--- a/packages/console/app/src/routes/brand/index.tsx
+++ b/packages/console/app/src/routes/brand/index.tsx
@@ -33,6 +33,7 @@ const brandAssets = "/opencode-brand-assets.zip"
export default function Brand() {
const i18n = useI18n()
+ const alt = i18n.t("brand.meta.description")
const downloadFile = async (url: string, filename: string) => {
try {
const response = await fetch(url)
@@ -88,7 +89,7 @@ export default function Brand() {
<div data-component="brand-grid">
<div>
- <img src={previewLogoLight} alt="OpenCode brand guidelines" />
+ <img src={previewLogoLight} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightPng, "opencode-logo-light.png")}>
PNG
@@ -115,7 +116,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewLogoDark} alt="OpenCode brand guidelines" />
+ <img src={previewLogoDark} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkPng, "opencode-logo-dark.png")}>
PNG
@@ -142,7 +143,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewLogoLightSquare} alt="OpenCode brand guidelines" />
+ <img src={previewLogoLightSquare} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(logoLightSquarePng, "opencode-logo-light-square.png")}>
PNG
@@ -169,7 +170,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewLogoDarkSquare} alt="OpenCode brand guidelines" />
+ <img src={previewLogoDarkSquare} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(logoDarkSquarePng, "opencode-logo-dark-square.png")}>
PNG
@@ -196,7 +197,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
+ <img src={previewWordmarkLight} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkLightPng, "opencode-wordmark-light.png")}>
PNG
@@ -223,7 +224,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewWordmarkDark} alt="OpenCode brand guidelines" />
+ <img src={previewWordmarkDark} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkDarkPng, "opencode-wordmark-dark.png")}>
PNG
@@ -250,7 +251,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewWordmarkSimpleLight} alt="OpenCode brand guidelines" />
+ <img src={previewWordmarkSimpleLight} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleLightPng, "opencode-wordmark-simple-light.png")}>
PNG
@@ -277,7 +278,7 @@ export default function Brand() {
</div>
</div>
<div>
- <img src={previewWordmarkSimpleDark} alt="OpenCode brand guidelines" />
+ <img src={previewWordmarkSimpleDark} alt={alt} />
<div data-component="actions">
<button onClick={() => downloadFile(wordmarkSimpleDarkPng, "opencode-wordmark-simple-dark.png")}>
PNG
diff --git a/packages/console/app/src/routes/download/[channel]/[platform].ts b/packages/console/app/src/routes/download/[channel]/[platform].ts
index 9a5284263..e9b3f23e7 100644
--- a/packages/console/app/src/routes/download/[channel]/[platform].ts
+++ b/packages/console/app/src/routes/download/[channel]/[platform].ts
@@ -19,7 +19,7 @@ const downloadNames: Record<string, string> = {
export async function GET({ params: { platform, channel } }: APIEvent) {
const assetName = assetNames[platform]
- if (!assetName) return new Response("Not Found", { status: 404 })
+ if (!assetName) return new Response(null, { status: 404 })
const resp = await fetch(
`https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/latest/download/${assetName}`,
diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts
index 47ca442ec..95cd9da21 100644
--- a/packages/console/app/src/routes/stripe/webhook.ts
+++ b/packages/console/app/src/routes/stripe/webhook.ts
@@ -306,7 +306,7 @@ export async function POST(input: APIEvent) {
.update(BillingTable)
.set({
reload: false,
- reloadError: errorMessage ?? "Payment failed.",
+ reloadError: errorMessage ?? "workspace.reload.error.paymentFailed",
timeReloadError: sql`now()`,
})
.where(eq(BillingTable.workspaceID, Actor.workspace())),
diff --git a/packages/console/app/src/routes/temp.tsx b/packages/console/app/src/routes/temp.tsx
index ac506928e..4eed47857 100644
--- a/packages/console/app/src/routes/temp.tsx
+++ b/packages/console/app/src/routes/temp.tsx
@@ -47,8 +47,8 @@ export default function Home() {
<div data-component="content">
<section data-component="top">
- <img data-slot="logo light" src={logoLight} alt="opencode logo light" />
- <img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
+ <img data-slot="logo light" src={logoLight} alt={i18n.t("temp.logoLightAlt")} />
+ <img data-slot="logo dark" src={logoDark} alt={i18n.t("temp.logoDarkAlt")} />
<h1 data-slot="title">{i18n.t("temp.hero.title")}</h1>
<div data-slot="login">
<a href="/auth">{i18n.t("temp.zen")}</a>
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
index 395d008e1..f67775d79 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
@@ -12,6 +12,7 @@ import { queryBillingInfo } from "../../common"
import styles from "./lite-section.module.css"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
+import { formError } from "~/lib/form-error"
const queryLiteSubscription = query(async (workspaceID: string) => {
"use server"
@@ -114,7 +115,7 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID")?.toString()
- if (!workspaceID) return { error: "Workspace ID is required" }
+ if (!workspaceID) return { error: formError.workspaceRequired }
const useBalance = form.get("useBalance")?.toString() === "true"
return json(
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
index 7084be133..90c9d7a2e 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
@@ -202,7 +202,8 @@ export function ReloadSection() {
minute: "2-digit",
second: "2-digit",
})}
- . {i18n.t("workspace.reload.reason")} {billingInfo()?.reloadError?.replace(/\.$/, "")}.{" "}
+ . {i18n.t("workspace.reload.reason")}{" "}
+ {localizeError(i18n.t, billingInfo()?.reloadError ?? undefined).replace(/\.$/, "")}.{" "}
{i18n.t("workspace.reload.updatePaymentMethod")}
</p>
<form action={reload} method="post" data-slot="create-form">
diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index 3f6d69bf2..d3a25c5f6 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -35,6 +35,8 @@ import { createTrialLimiter } from "./trialLimiter"
import { createStickyTracker } from "./stickyProviderTracker"
import { LiteData } from "@opencode-ai/console-core/lite.js"
import { Resource } from "@opencode-ai/console-resource"
+import { i18n, type Key } from "~/i18n"
+import { localeFromRequest } from "~/lib/language"
type ZenData = Awaited<ReturnType<typeof ZenData.list>>
type RetryOptions = {
@@ -43,6 +45,15 @@ type RetryOptions = {
}
type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "lite" | "balance"
+function resolve(text: string, params?: Record<string, string | number>) {
+ if (!params) return text
+ return text.replace(/\{\{(\w+)\}\}/g, (raw, key) => {
+ const value = params[key]
+ if (value === undefined || value === null) return raw
+ return String(value)
+ })
+}
+
export async function handler(
input: APIEvent,
opts: {
@@ -60,6 +71,8 @@ export async function handler(
const MAX_FAILOVER_RETRIES = 3
const MAX_429_RETRIES = 3
+ const dict = i18n(localeFromRequest(input.request))
+ const t = (key: Key, params?: Record<string, string | number>) => resolve(dict[key], params)
const ADMIN_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
"wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
@@ -86,7 +99,7 @@ export async function handler(
const dataDumper = createDataDumper(sessionId, requestId, projectId)
const trialLimiter = createTrialLimiter(modelInfo.trial, ip, ocClient)
const isTrial = await trialLimiter?.isTrial()
- const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request.headers)
+ const rateLimiter = createRateLimiter(modelInfo.rateLimit, ip, input.request)
await rateLimiter?.check()
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
const stickyProvider = await stickyTracker?.get()
@@ -359,14 +372,20 @@ export async function handler(
}
function validateModel(zenData: ZenData, reqModel: string) {
- if (!(reqModel in zenData.models)) throw new ModelError(`Model ${reqModel} not supported`)
+ if (!(reqModel in zenData.models)) throw new ModelError(t("zen.api.error.modelNotSupported", { model: reqModel }))
const modelId = reqModel as keyof typeof zenData.models
const modelData = Array.isArray(zenData.models[modelId])
? zenData.models[modelId].find((model) => opts.format === model.formatFilter)
: zenData.models[modelId]
- if (!modelData) throw new ModelError(`Model ${reqModel} not supported for format ${opts.format}`)
+ if (!modelData)
+ throw new ModelError(
+ t("zen.api.error.modelFormatNotSupported", {
+ model: reqModel,
+ format: opts.format,
+ }),
+ )
logger.metric({ model: modelId })
@@ -418,8 +437,9 @@ export async function handler(
return modelInfo.providers.find((provider) => provider.id === modelInfo.fallbackProvider)
})()
- if (!modelProvider) throw new ModelError("No provider available")
- if (!(modelProvider.id in zenData.providers)) throw new ModelError(`Provider ${modelProvider.id} not supported`)
+ if (!modelProvider) throw new ModelError(t("zen.api.error.noProviderAvailable"))
+ if (!(modelProvider.id in zenData.providers))
+ throw new ModelError(t("zen.api.error.providerNotSupported", { provider: modelProvider.id }))
return {
...modelProvider,
@@ -439,7 +459,7 @@ export async function handler(
const apiKey = opts.parseApiKey(input.request.headers)
if (!apiKey || apiKey === "public") {
if (modelInfo.allowAnonymous) return
- throw new AuthError("Missing API key.")
+ throw new AuthError(t("zen.api.error.missingApiKey"))
}
const data = await Database.use((tx) =>
@@ -520,13 +540,13 @@ export async function handler(
.then((rows) => rows[0]),
)
- if (!data) throw new AuthError("Invalid API key.")
+ if (!data) throw new AuthError(t("zen.api.error.invalidApiKey"))
if (
modelInfo.id.startsWith("alpha-") &&
Resource.App.stage === "production" &&
!ADMIN_WORKSPACES.includes(data.workspaceID)
)
- throw new AuthError(`Model ${modelInfo.id} not supported`)
+ throw new AuthError(t("zen.api.error.modelNotSupported", { model: modelInfo.id }))
logger.metric({
api_key: data.apiKey,
@@ -590,7 +610,9 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
- `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+ t("zen.api.error.subscriptionQuotaExceeded", {
+ retryIn: formatRetryTime(result.resetInSec),
+ }),
result.resetInSec,
)
}
@@ -606,7 +628,9 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
- `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+ t("zen.api.error.subscriptionQuotaExceeded", {
+ retryIn: formatRetryTime(result.resetInSec),
+ }),
result.resetInSec,
)
}
@@ -632,7 +656,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
- `Subscription quota exceeded. You can continue using free models.`,
+ t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
result.resetInSec,
)
}
@@ -647,7 +671,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
- `Subscription quota exceeded. You can continue using free models.`,
+ t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
result.resetInSec,
)
}
@@ -662,7 +686,7 @@ export async function handler(
})
if (result.status === "rate-limited")
throw new SubscriptionUsageLimitError(
- `Subscription quota exceeded. You can continue using free models.`,
+ t("zen.api.error.subscriptionQuotaExceededUseFreeModels"),
result.resetInSec,
)
}
@@ -675,14 +699,10 @@ export async function handler(
// Validate pay as you go billing
const billing = authInfo.billing
- if (!billing.paymentMethodID)
- throw new CreditsError(
- `No payment method. Add a payment method here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
- )
- if (billing.balance <= 0)
- throw new CreditsError(
- `Insufficient balance. Manage your billing here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
- )
+ const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing`
+ const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members`
+ if (!billing.paymentMethodID) throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl }))
+ if (billing.balance <= 0) throw new CreditsError(t("zen.api.error.insufficientBalance", { billingUrl }))
const now = new Date()
const currentYear = now.getUTCFullYear()
@@ -696,7 +716,10 @@ export async function handler(
currentMonth === billing.timeMonthlyUsageUpdated.getUTCMonth()
)
throw new MonthlyLimitError(
- `Your workspace has reached its monthly spending limit of $${billing.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
+ t("zen.api.error.workspaceMonthlyLimitReached", {
+ amount: billing.monthlyLimit,
+ billingUrl,
+ }),
)
if (
@@ -708,7 +731,10 @@ export async function handler(
currentMonth === authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()
)
throw new UserLimitError(
- `You have reached your monthly spending limit of $${authInfo.user.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/members`,
+ t("zen.api.error.userMonthlyLimitReached", {
+ amount: authInfo.user.monthlyLimit,
+ membersUrl,
+ }),
)
return "balance"
@@ -716,7 +742,7 @@ export async function handler(
function validateModelSettings(authInfo: AuthInfo) {
if (!authInfo) return
- if (authInfo.isDisabled) throw new ModelError("Model is disabled")
+ if (authInfo.isDisabled) throw new ModelError(t("zen.api.error.modelDisabled"))
}
function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {
diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts
index 5e4f31e67..6325a7b4d 100644
--- a/packages/console/app/src/routes/zen/util/rateLimiter.ts
+++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts
@@ -3,11 +3,14 @@ import { IpRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
import { FreeUsageLimitError } from "./error"
import { logger } from "./logger"
import { ZenData } from "@opencode-ai/console-core/model.js"
+import { i18n } from "~/i18n"
+import { localeFromRequest } from "~/lib/language"
-export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, headers: Headers) {
+export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: string, request: Request) {
if (!limit) return
+ const dict = i18n(localeFromRequest(request))
- const limitValue = limit.checkHeader && !headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value
+ const limitValue = limit.checkHeader && !request.headers.get(limit.checkHeader) ? limit.fallbackValue! : limit.value
const ip = !rawIp.length ? "unknown" : rawIp
const now = Date.now()
@@ -36,7 +39,7 @@ export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: s
logger.debug(`rate limit total: ${total}`)
if (total >= limitValue)
throw new FreeUsageLimitError(
- `Rate limit exceeded. Please try again later.`,
+ dict["zen.api.error.rateLimitExceeded"],
limit.period === "day" ? getRetryAfterDay(now) : getRetryAfterHour(rows, intervals, limitValue, now),
)
},