feat: add Russian localization and independent STT languages
This commit is contained in:
+17
-16
@@ -1,13 +1,15 @@
|
||||
"use client";
|
||||
import { useI18n } from "@/components/i18n";
|
||||
|
||||
|
||||
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { subscriptionLabels, subscriptionLabel, type SessionPricing, type TurnUsage } from "@/src/core";
|
||||
import { subscriptionLabels, subscriptionLabel, type Language, type SessionPricing, type TurnUsage } from "@/src/core";
|
||||
import { formatTokens, formatUsd, planCostLabel, prettyModel } from "@/components/usage-format";
|
||||
import { ModelControl, catalogLimits, useModelCatalog } from "@/components/model-picker";
|
||||
|
||||
export function formatContextMeasurement(tokens: number | null, limit: number | null): string {
|
||||
const numerator = tokens === null ? "?" : formatTokens(tokens);
|
||||
return limit === null ? numerator : `${numerator} / ${formatTokens(limit)}`;
|
||||
export function formatContextMeasurement(tokens: number | null, limit: number | null, language: Language = "en"): string {
|
||||
const numerator = tokens === null ? "?" : formatTokens(tokens, language);
|
||||
return limit === null ? numerator : `${numerator} / ${formatTokens(limit, language)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,7 @@ export function StatsBar({
|
||||
* data (which renders nothing pre-chat on that surface). */
|
||||
leadingSlot?: ReactNode;
|
||||
}) {
|
||||
const { t, language } = useI18n();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedModel, setSelectedModel] = useState<string | undefined>();
|
||||
// Same dropdown discipline as the model control (owner, df4433f treatment,
|
||||
@@ -84,13 +87,13 @@ export function StatsBar({
|
||||
const { catalog } = useModelCatalog();
|
||||
const limits = useMemo(() => (catalog ? catalogLimits(catalog) : undefined), [catalog]);
|
||||
const limit = selectedModel ? limits?.get(selectedModel) ?? null : null;
|
||||
const contextLabel = formatContextMeasurement(contextTokens, limit);
|
||||
const contextLabel = formatContextMeasurement(contextTokens, limit, language);
|
||||
const { cost = 0, unpriced = turns.length, subscription = 0 } = pricing ?? {};
|
||||
// Honest cost: show dollars only when at least one turn priced;
|
||||
// otherwise "—" (never a fake $0.0000).
|
||||
const pricedCount = turns.length - unpriced - subscription;
|
||||
const providers = pricing?.subscriptionProviders ?? subscriptionLabels(turns.map((turn) => turn.model));
|
||||
const costLabel = planCostLabel(pricedCount > 0 ? cost : null, providers);
|
||||
const costLabel = planCostLabel(pricedCount > 0 ? cost : null, providers, (value) => formatUsd(value, language));
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -135,23 +138,22 @@ export function StatsBar({
|
||||
className="absolute bottom-full left-1/2 z-40 mb-1.5 w-72 -translate-x-1/2 rounded-xl border bg-card p-3 text-xs shadow-xl"
|
||||
>
|
||||
<div className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Session stats
|
||||
</div>
|
||||
{t("Session stats")}</div>
|
||||
<div className="flex flex-col gap-1 text-muted-foreground">
|
||||
<div className="flex justify-between gap-2">
|
||||
<span>Context (last call)</span>
|
||||
<span>{t("Context (last call)")}</span>
|
||||
<span className="tabular-nums text-card-foreground">
|
||||
{contextLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2">
|
||||
<span>{subscription ? "Usage" : "Total cost"}</span>
|
||||
<span>{subscription ? t("Usage") : t("Total cost")}</span>
|
||||
<span className="tabular-nums text-card-foreground">{costLabel}</span>
|
||||
</div>
|
||||
{providers.includes("ChatGPT") && <a href="https://chatgpt.com/settings/usage" target="_blank" rel="noreferrer" className="underline underline-offset-2">Manage ChatGPT usage</a>}
|
||||
{providers.includes("ChatGPT") && <a href="https://chatgpt.com/settings/usage" target="_blank" rel="noreferrer" className="underline underline-offset-2">{t("Manage ChatGPT usage")}</a>}
|
||||
{unpriced > 0 && (
|
||||
<div className="flex justify-between gap-2">
|
||||
<span>Unpriced turns</span>
|
||||
<span>{t("Unpriced turns")}</span>
|
||||
<span className="tabular-nums text-card-foreground">{unpriced}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -159,8 +161,7 @@ export function StatsBar({
|
||||
{turns.length > 0 && (
|
||||
<>
|
||||
<div className="mb-1 mt-2.5 border-t pt-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Turns
|
||||
</div>
|
||||
{t("Turns")}</div>
|
||||
<ul className="flex max-h-56 flex-col gap-1 overflow-y-auto">
|
||||
{turns.map((turn, i) => {
|
||||
const c = pricing?.turnCosts[turn.id] ?? null;
|
||||
@@ -170,8 +171,8 @@ export function StatsBar({
|
||||
#{i + 1} · {prettyModel(turn.model, true)}
|
||||
</span>
|
||||
<span className="flex shrink-0 items-center gap-2 tabular-nums text-card-foreground">
|
||||
<span className="text-muted-foreground">{turn.steps.length} steps</span>
|
||||
<span>{subscriptionLabel(turn.model) ?? (c === null ? "—" : formatUsd(c))}</span>
|
||||
<span className="text-muted-foreground">{turn.steps.length}{" "}{t("steps")}</span>
|
||||
<span>{subscriptionLabel(turn.model) ?? (c === null ? "—" : formatUsd(c, language))}</span>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user