Compare commits
4 Commits
feat/B6-se
...
feat/B7-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
452d7d9b3d
|
|||
|
21eb211d6a
|
|||
|
0de99a8cc7
|
|||
|
1bf3348c8c
|
@@ -66,6 +66,7 @@ jobs:
|
|||||||
build_cortex: ${{ steps.changes.outputs.build_cortex }}
|
build_cortex: ${{ steps.changes.outputs.build_cortex }}
|
||||||
build_neuron: ${{ steps.changes.outputs.build_neuron }}
|
build_neuron: ${{ steps.changes.outputs.build_neuron }}
|
||||||
build_bench: ${{ steps.changes.outputs.build_bench }}
|
build_bench: ${{ steps.changes.outputs.build_bench }}
|
||||||
|
build_upstream: ${{ steps.changes.outputs.build_upstream }}
|
||||||
check_rust: ${{ steps.changes.outputs.check_rust }}
|
check_rust: ${{ steps.changes.outputs.check_rust }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -104,6 +105,7 @@ jobs:
|
|||||||
BUILD_CORTEX=true
|
BUILD_CORTEX=true
|
||||||
BUILD_NEURON=true
|
BUILD_NEURON=true
|
||||||
BUILD_BENCH=true
|
BUILD_BENCH=true
|
||||||
|
BUILD_UPSTREAM=true
|
||||||
CHECK_RUST=true
|
CHECK_RUST=true
|
||||||
|
|
||||||
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
|
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
|
||||||
@@ -149,6 +151,7 @@ jobs:
|
|||||||
NEURON_RE='^crates/neuron/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/helexa-neuron-prerelease\.spec$|^data/neuron|^neuron\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
NEURON_RE='^crates/neuron/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/helexa-neuron-prerelease\.spec$|^data/neuron|^neuron\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
||||||
CORTEX_RE='^crates/cortex-gateway/|^crates/cortex-cli/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/cortex-prerelease\.spec$|^data/cortex|^cortex\.example\.toml$|^models\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
CORTEX_RE='^crates/cortex-gateway/|^crates/cortex-cli/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/cortex-prerelease\.spec$|^data/cortex|^cortex\.example\.toml$|^models\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
||||||
BENCH_RE='^crates/helexa-bench/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/helexa-bench-prerelease\.spec$|^data/helexa-bench|^helexa-bench\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
BENCH_RE='^crates/helexa-bench/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/helexa-bench-prerelease\.spec$|^data/helexa-bench|^helexa-bench\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
||||||
|
UPSTREAM_RE='^crates/helexa-upstream/|^crates/cortex-core/|^Cargo\.toml$|^Cargo\.lock$|^rpm/helexa-upstream-prerelease\.spec$|^data/helexa-upstream|^helexa-upstream\.example\.toml$|^\.gitea/workflows/build-prerelease\.yml$'
|
||||||
# Any Rust change (incl. crates not packaged here, e.g.
|
# Any Rust change (incl. crates not packaged here, e.g.
|
||||||
# helexa-acp) still needs lint+test on main.
|
# helexa-acp) still needs lint+test on main.
|
||||||
RUST_RE='\.rs$|^crates/|Cargo\.toml$|^Cargo\.lock$'
|
RUST_RE='\.rs$|^crates/|Cargo\.toml$|^Cargo\.lock$'
|
||||||
@@ -156,10 +159,12 @@ jobs:
|
|||||||
CORTEX_BASE=$(base_for cortex)
|
CORTEX_BASE=$(base_for cortex)
|
||||||
NEURON_BASE=$(base_for helexa-neuron-blackwell)
|
NEURON_BASE=$(base_for helexa-neuron-blackwell)
|
||||||
BENCH_BASE=$(base_for helexa-bench)
|
BENCH_BASE=$(base_for helexa-bench)
|
||||||
|
UPSTREAM_BASE=$(base_for helexa-upstream)
|
||||||
BUILD_CORTEX=$(decide "$CORTEX_BASE" "$CORTEX_RE")
|
BUILD_CORTEX=$(decide "$CORTEX_BASE" "$CORTEX_RE")
|
||||||
BUILD_NEURON=$(decide "$NEURON_BASE" "$NEURON_RE")
|
BUILD_NEURON=$(decide "$NEURON_BASE" "$NEURON_RE")
|
||||||
BUILD_BENCH=$(decide "$BENCH_BASE" "$BENCH_RE")
|
BUILD_BENCH=$(decide "$BENCH_BASE" "$BENCH_RE")
|
||||||
if [ "$BUILD_CORTEX" = "true" ] || [ "$BUILD_NEURON" = "true" ] || [ "$BUILD_BENCH" = "true" ]; then
|
BUILD_UPSTREAM=$(decide "$UPSTREAM_BASE" "$UPSTREAM_RE")
|
||||||
|
if [ "$BUILD_CORTEX" = "true" ] || [ "$BUILD_NEURON" = "true" ] || [ "$BUILD_BENCH" = "true" ] || [ "$BUILD_UPSTREAM" = "true" ]; then
|
||||||
CHECK_RUST=true
|
CHECK_RUST=true
|
||||||
else
|
else
|
||||||
CHECK_RUST=$(decide "$CORTEX_BASE" "$RUST_RE")
|
CHECK_RUST=$(decide "$CORTEX_BASE" "$RUST_RE")
|
||||||
@@ -170,8 +175,9 @@ jobs:
|
|||||||
echo "build_cortex=${BUILD_CORTEX}" >> "$GITHUB_OUTPUT"
|
echo "build_cortex=${BUILD_CORTEX}" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_neuron=${BUILD_NEURON}" >> "$GITHUB_OUTPUT"
|
echo "build_neuron=${BUILD_NEURON}" >> "$GITHUB_OUTPUT"
|
||||||
echo "build_bench=${BUILD_BENCH}" >> "$GITHUB_OUTPUT"
|
echo "build_bench=${BUILD_BENCH}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "build_upstream=${BUILD_UPSTREAM}" >> "$GITHUB_OUTPUT"
|
||||||
echo "check_rust=${CHECK_RUST}" >> "$GITHUB_OUTPUT"
|
echo "check_rust=${CHECK_RUST}" >> "$GITHUB_OUTPUT"
|
||||||
echo "### change detection: build_cortex=${BUILD_CORTEX} build_neuron=${BUILD_NEURON} build_bench=${BUILD_BENCH} check_rust=${CHECK_RUST}"
|
echo "### change detection: build_cortex=${BUILD_CORTEX} build_neuron=${BUILD_NEURON} build_bench=${BUILD_BENCH} build_upstream=${BUILD_UPSTREAM} check_rust=${CHECK_RUST}"
|
||||||
|
|
||||||
# fmt + clippy + test moved here from ci.yml for main pushes so the
|
# fmt + clippy + test moved here from ci.yml for main pushes so the
|
||||||
# two workflows stop queueing against each other (ci.yml's checks
|
# two workflows stop queueing against each other (ci.yml's checks
|
||||||
@@ -303,6 +309,45 @@ jobs:
|
|||||||
path: artifacts/helexa-bench
|
path: artifacts/helexa-bench
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
build-upstream:
|
||||||
|
name: Build helexa-upstream binary
|
||||||
|
timeout-minutes: 25
|
||||||
|
needs: prepare
|
||||||
|
if: needs.prepare.outputs.build_upstream == 'true'
|
||||||
|
# Pure-Rust, non-CUDA binary — same runner as cortex/bench.
|
||||||
|
runs-on: rust
|
||||||
|
env:
|
||||||
|
RUSTC_WRAPPER: sccache
|
||||||
|
SCCACHE_BUCKET: sccache
|
||||||
|
SCCACHE_ENDPOINT: http://caveman.kosherinata.internal:9000
|
||||||
|
SCCACHE_REGION: auto
|
||||||
|
SCCACHE_S3_USE_SSL: "false"
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_KEY }}
|
||||||
|
# helexa-upstream uses the sqlx runtime query API (no compile-time
|
||||||
|
# query macros), so it builds without a database or a .sqlx cache.
|
||||||
|
# Set OFFLINE defensively so a stray macro can never reach for a DB.
|
||||||
|
SQLX_OFFLINE: "true"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Build helexa-upstream (release, sccache escalation)
|
||||||
|
run: script/ci-cargo-escalate.sh cargo build --release -p helexa-upstream
|
||||||
|
|
||||||
|
- name: Stage binary
|
||||||
|
run: |
|
||||||
|
mkdir --parents artifacts
|
||||||
|
cp target/release/helexa-upstream artifacts/helexa-upstream
|
||||||
|
./artifacts/helexa-upstream --version || true
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: upstream-fc43
|
||||||
|
path: artifacts/helexa-upstream
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
build-neuron:
|
build-neuron:
|
||||||
name: Build neuron-${{ matrix.flavour }}
|
name: Build neuron-${{ matrix.flavour }}
|
||||||
timeout-minutes: 35
|
timeout-minutes: 35
|
||||||
@@ -459,6 +504,44 @@ jobs:
|
|||||||
path: ~/rpmbuild/RPMS/x86_64/*.rpm
|
path: ~/rpmbuild/RPMS/x86_64/*.rpm
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
package-upstream:
|
||||||
|
name: Package helexa-upstream RPM
|
||||||
|
timeout-minutes: 20
|
||||||
|
needs: [prepare, build-upstream]
|
||||||
|
runs-on: rpm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: upstream-fc43
|
||||||
|
path: artifacts/
|
||||||
|
|
||||||
|
- name: Build RPM
|
||||||
|
run: |
|
||||||
|
set -eux
|
||||||
|
rm -f ~/.rpmmacros
|
||||||
|
rpmdev-setuptree
|
||||||
|
cp artifacts/helexa-upstream ~/rpmbuild/SOURCES/
|
||||||
|
cp data/helexa-upstream.service ~/rpmbuild/SOURCES/
|
||||||
|
cp data/helexa-upstream-sysusers.conf ~/rpmbuild/SOURCES/
|
||||||
|
cp data/helexa-upstream-firewalld.xml ~/rpmbuild/SOURCES/
|
||||||
|
cp helexa-upstream.example.toml ~/rpmbuild/SOURCES/
|
||||||
|
cp LICENSE ~/rpmbuild/SOURCES/
|
||||||
|
rpmbuild -bb rpm/helexa-upstream-prerelease.spec \
|
||||||
|
--define "upstream_version ${{ needs.prepare.outputs.version }}" \
|
||||||
|
--define "upstream_prerelease ${{ needs.prepare.outputs.release }}" \
|
||||||
|
--undefine dist \
|
||||||
|
--define "dist .fc43"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rpm-upstream-fc43
|
||||||
|
path: ~/rpmbuild/RPMS/x86_64/*.rpm
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
package-neuron:
|
package-neuron:
|
||||||
name: Package helexa-neuron-${{ matrix.flavour }} RPM
|
name: Package helexa-neuron-${{ matrix.flavour }} RPM
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -508,7 +591,7 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
name: Publish to rpm.lair.cafe (unstable)
|
name: Publish to rpm.lair.cafe (unstable)
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
needs: [lint, test, package-cortex, package-neuron, package-bench]
|
needs: [lint, test, package-cortex, package-neuron, package-bench, package-upstream]
|
||||||
# Runs when at least one package was built and nothing failed.
|
# Runs when at least one package was built and nothing failed.
|
||||||
# lint/test may be skipped (docs-only refs never get here because
|
# lint/test may be skipped (docs-only refs never get here because
|
||||||
# no packages build), but a real failure in any blocks the
|
# no packages build), but a real failure in any blocks the
|
||||||
@@ -518,10 +601,11 @@ jobs:
|
|||||||
!cancelled()
|
!cancelled()
|
||||||
&& (needs.lint.result == 'success' || needs.lint.result == 'skipped')
|
&& (needs.lint.result == 'success' || needs.lint.result == 'skipped')
|
||||||
&& (needs.test.result == 'success' || needs.test.result == 'skipped')
|
&& (needs.test.result == 'success' || needs.test.result == 'skipped')
|
||||||
&& (needs.package-cortex.result == 'success' || needs.package-neuron.result == 'success' || needs.package-bench.result == 'success')
|
&& (needs.package-cortex.result == 'success' || needs.package-neuron.result == 'success' || needs.package-bench.result == 'success' || needs.package-upstream.result == 'success')
|
||||||
&& needs.package-cortex.result != 'failure'
|
&& needs.package-cortex.result != 'failure'
|
||||||
&& needs.package-neuron.result != 'failure'
|
&& needs.package-neuron.result != 'failure'
|
||||||
&& needs.package-bench.result != 'failure'
|
&& needs.package-bench.result != 'failure'
|
||||||
|
&& needs.package-upstream.result != 'failure'
|
||||||
}}
|
}}
|
||||||
runs-on: rpm
|
runs-on: rpm
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
6
data/helexa-upstream-firewalld.xml
Normal file
6
data/helexa-upstream-firewalld.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<service>
|
||||||
|
<short>helexa-upstream</short>
|
||||||
|
<description>helexa-upstream — mesh account + budget authority API (/authz/v1 for cortex, /web/v1 for the frontend)</description>
|
||||||
|
<port protocol="tcp" port="8090"/>
|
||||||
|
</service>
|
||||||
3
data/helexa-upstream-sysusers.conf
Normal file
3
data/helexa-upstream-sysusers.conf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
g helexa-upstream - -
|
||||||
|
u helexa-upstream - "helexa-upstream authority" /var/lib/helexa-upstream /sbin/nologin
|
||||||
|
m helexa-upstream helexa-upstream
|
||||||
22
data/helexa-upstream.service
Normal file
22
data/helexa-upstream.service
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=helexa-upstream — mesh account + budget authority (accounts, API keys, allocation ledger, top-up codes)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/helexa-upstream serve --config /etc/helexa-upstream/helexa-upstream.toml
|
||||||
|
# HTTP authority for cortex (/authz/v1) and the frontend (/web/v1); restart
|
||||||
|
# unconditionally if it ever exits. It connects out to PostgreSQL (the
|
||||||
|
# system of record) and runs schema migrations on startup.
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
User=helexa-upstream
|
||||||
|
Group=helexa-upstream
|
||||||
|
# Service user home; no local state (PostgreSQL holds everything), but
|
||||||
|
# StateDirectory gives the user a writable, correctly-owned home.
|
||||||
|
StateDirectory=helexa-upstream
|
||||||
|
StateDirectoryMode=0755
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -23,7 +23,7 @@ const ROOT = path.resolve(
|
|||||||
const RESOURCES_DIR = path.join(ROOT, "src", "i18n", "resources");
|
const RESOURCES_DIR = path.join(ROOT, "src", "i18n", "resources");
|
||||||
|
|
||||||
// Namespaces to validate.
|
// Namespaces to validate.
|
||||||
const NAMESPACES = ["common", "mission", "chat"];
|
const NAMESPACES = ["common", "mission", "chat", "account"];
|
||||||
|
|
||||||
// Languages to validate should track SUPPORTED_LANGUAGES in src/i18n/languages.ts.
|
// Languages to validate should track SUPPORTED_LANGUAGES in src/i18n/languages.ts.
|
||||||
// NOTE: This list is intentionally narrower than SUPPORTED_LANGUAGES and does not
|
// NOTE: This list is intentionally narrower than SUPPORTED_LANGUAGES and does not
|
||||||
|
|||||||
@@ -1,26 +1,58 @@
|
|||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
import ThemeProvider from "./layout/ThemeProvider";
|
import ThemeProvider from "./layout/ThemeProvider";
|
||||||
|
import AuthProvider from "./auth/AuthProvider";
|
||||||
|
import RequireAuth from "./auth/RequireAuth";
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
import Mission from "./pages/Mission";
|
import Mission from "./pages/Mission";
|
||||||
import Chat from "./pages/Chat";
|
import Chat from "./pages/Chat";
|
||||||
|
import Login from "./pages/auth/Login";
|
||||||
|
import Register from "./pages/auth/Register";
|
||||||
|
import VerifyEmail from "./pages/auth/VerifyEmail";
|
||||||
|
import RequestReset from "./pages/auth/RequestReset";
|
||||||
|
import ResetPassword from "./pages/auth/ResetPassword";
|
||||||
|
import Dashboard from "./pages/account/Dashboard";
|
||||||
|
import ApiKeys from "./pages/account/ApiKeys";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
// Composition root: theme + router + layout shell. `/` is the chat
|
// Composition root: theme → router → auth → layout shell. `/` is the chat
|
||||||
// workspace (F3, anonymous for now); `/mission` (F2) is the EU-sovereignty
|
// workspace (F3); `/mission` the EU-sovereignty narrative (F2); the auth +
|
||||||
// narrative; the auth/account routes (F4) land next.
|
// account routes (F4) follow, with /account guarded.
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className="d-flex flex-column min-vh-100">
|
<AuthProvider>
|
||||||
<Header />
|
<div className="d-flex flex-column min-vh-100">
|
||||||
<Routes>
|
<Header />
|
||||||
<Route path="/" element={<Chat />} />
|
<Routes>
|
||||||
<Route path="/mission" element={<Mission />} />
|
<Route path="/" element={<Chat />} />
|
||||||
</Routes>
|
<Route path="/mission" element={<Mission />} />
|
||||||
<Footer />
|
<Route path="/login" element={<Login />} />
|
||||||
</div>
|
<Route path="/register" element={<Register />} />
|
||||||
|
<Route path="/verify" element={<VerifyEmail />} />
|
||||||
|
<Route path="/forgot" element={<RequestReset />} />
|
||||||
|
<Route path="/reset" element={<ResetPassword />} />
|
||||||
|
<Route
|
||||||
|
path="/account"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<Dashboard />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/account/keys"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<ApiKeys />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</AuthProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
213
helexa.ai/src/api/account.ts
Normal file
213
helexa.ai/src/api/account.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
// Account API client over helexa-upstream's /web/v1 (B4/B5). The browser
|
||||||
|
// calls a same-origin `/api` prefix (vite-proxied in dev, nginx-routed in
|
||||||
|
// prod). A MockAccountApi behind VITE_USE_MOCK_ACCOUNT_API lets the
|
||||||
|
// dashboard be built/demoed before the upstream service is reachable.
|
||||||
|
|
||||||
|
import {
|
||||||
|
ApiError,
|
||||||
|
type AccountBalance,
|
||||||
|
type ApiKeySummary,
|
||||||
|
type CreatedKey,
|
||||||
|
type Session,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export interface AccountApi {
|
||||||
|
register(email: string, password: string, fingerprint?: string): Promise<void>;
|
||||||
|
verify(token: string): Promise<void>;
|
||||||
|
login(email: string, password: string): Promise<Session>;
|
||||||
|
requestReset(email: string): Promise<void>;
|
||||||
|
confirmReset(token: string, newPassword: string): Promise<void>;
|
||||||
|
account(token: string): Promise<AccountBalance>;
|
||||||
|
listKeys(token: string): Promise<ApiKeySummary[]>;
|
||||||
|
createKey(
|
||||||
|
token: string,
|
||||||
|
label: string,
|
||||||
|
limitKind: "percent" | "hardcap",
|
||||||
|
limitValue: number,
|
||||||
|
): Promise<CreatedKey>;
|
||||||
|
archiveKey(token: string, id: string): Promise<void>;
|
||||||
|
updateKeyLimit(
|
||||||
|
token: string,
|
||||||
|
id: string,
|
||||||
|
limitKind: "percent" | "hardcap",
|
||||||
|
limitValue: number,
|
||||||
|
): Promise<void>;
|
||||||
|
redeem(token: string, code: string): Promise<AccountBalance>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE = (import.meta.env.VITE_ACCOUNT_BASE_URL || "/api").replace(/\/$/, "");
|
||||||
|
|
||||||
|
async function call<T>(
|
||||||
|
path: string,
|
||||||
|
init: RequestInit & { token?: string } = {},
|
||||||
|
): Promise<T> {
|
||||||
|
const headers: Record<string, string> = { "content-type": "application/json" };
|
||||||
|
if (init.token) headers.authorization = `Bearer ${init.token}`;
|
||||||
|
let resp: Response;
|
||||||
|
try {
|
||||||
|
resp = await fetch(`${BASE}${path}`, { ...init, headers });
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(0, "network_error", "Could not reach the account service.");
|
||||||
|
}
|
||||||
|
if (resp.status === 204) return undefined as T;
|
||||||
|
let body: unknown = null;
|
||||||
|
try {
|
||||||
|
body = await resp.json();
|
||||||
|
} catch {
|
||||||
|
/* empty body */
|
||||||
|
}
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = (body as { error?: { code?: string; message?: string } })?.error;
|
||||||
|
throw new ApiError(resp.status, err?.code ?? "error", err?.message ?? "Request failed.");
|
||||||
|
}
|
||||||
|
return body as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RealAccountApi implements AccountApi {
|
||||||
|
async register(email: string, password: string, fingerprint?: string) {
|
||||||
|
await call("/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ email, password, fingerprint }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async verify(token: string) {
|
||||||
|
await call("/verify", { method: "POST", body: JSON.stringify({ token }) });
|
||||||
|
}
|
||||||
|
login(email: string, password: string) {
|
||||||
|
return call<Session>("/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ email, password }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async requestReset(email: string) {
|
||||||
|
await call("/password-reset/request", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ email }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async confirmReset(token: string, newPassword: string) {
|
||||||
|
await call("/password-reset/confirm", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ token, new_password: newPassword }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
account(token: string) {
|
||||||
|
return call<AccountBalance>("/account", { token });
|
||||||
|
}
|
||||||
|
listKeys(token: string) {
|
||||||
|
return call<{ keys: ApiKeySummary[] }>("/keys", { token }).then((r) => r.keys);
|
||||||
|
}
|
||||||
|
createKey(token: string, label: string, limit_kind: "percent" | "hardcap", limit_value: number) {
|
||||||
|
return call<CreatedKey>("/keys", {
|
||||||
|
method: "POST",
|
||||||
|
token,
|
||||||
|
body: JSON.stringify({ label, limit_kind, limit_value }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async archiveKey(token: string, id: string) {
|
||||||
|
await call(`/keys/${id}/archive`, { method: "POST", token, body: "{}" });
|
||||||
|
}
|
||||||
|
async updateKeyLimit(
|
||||||
|
token: string,
|
||||||
|
id: string,
|
||||||
|
limit_kind: "percent" | "hardcap",
|
||||||
|
limit_value: number,
|
||||||
|
) {
|
||||||
|
await call(`/keys/${id}/limit`, {
|
||||||
|
method: "PATCH",
|
||||||
|
token,
|
||||||
|
body: JSON.stringify({ limit_kind, limit_value }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
redeem(token: string, code: string) {
|
||||||
|
return call<AccountBalance>("/redeem", {
|
||||||
|
method: "POST",
|
||||||
|
token,
|
||||||
|
body: JSON.stringify({ code }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Mock (VITE_USE_MOCK_ACCOUNT_API) ────────────────────────────────
|
||||||
|
// Minimal in-memory account so the dashboard is fully developable offline.
|
||||||
|
|
||||||
|
class MockAccountApi implements AccountApi {
|
||||||
|
private total = 1_000_000;
|
||||||
|
private spent = 0;
|
||||||
|
private reserved = 0;
|
||||||
|
private keys: ApiKeySummary[] = [];
|
||||||
|
private seq = 1;
|
||||||
|
|
||||||
|
async register() {}
|
||||||
|
async verify() {}
|
||||||
|
async login(): Promise<Session> {
|
||||||
|
return { token: "mock-token", expires_in: 604800 };
|
||||||
|
}
|
||||||
|
async requestReset() {}
|
||||||
|
async confirmReset() {}
|
||||||
|
async account(): Promise<AccountBalance> {
|
||||||
|
return {
|
||||||
|
account_id: "mock-account",
|
||||||
|
allocation_total: this.total,
|
||||||
|
allocation_spent: this.spent,
|
||||||
|
allocation_reserved: this.reserved,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async listKeys(): Promise<ApiKeySummary[]> {
|
||||||
|
return [...this.keys];
|
||||||
|
}
|
||||||
|
async createKey(
|
||||||
|
_t: string,
|
||||||
|
label: string,
|
||||||
|
limit_kind: "percent" | "hardcap",
|
||||||
|
limit_value: number,
|
||||||
|
): Promise<CreatedKey> {
|
||||||
|
const id = `mock-${this.seq++}`;
|
||||||
|
const prefix = `sk-helexa-mock${this.seq}`;
|
||||||
|
this.keys.push({
|
||||||
|
id,
|
||||||
|
prefix,
|
||||||
|
label,
|
||||||
|
status: "active",
|
||||||
|
limit_kind,
|
||||||
|
limit_value,
|
||||||
|
spent: 0,
|
||||||
|
reserved: 0,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
return { id, key: `${prefix}-RAWSECRETSHOWNONCE`, prefix, limit_kind, limit_value };
|
||||||
|
}
|
||||||
|
async archiveKey(_t: string, id: string) {
|
||||||
|
const k = this.keys.find((x) => x.id === id);
|
||||||
|
if (k) k.status = "archived";
|
||||||
|
}
|
||||||
|
async updateKeyLimit(
|
||||||
|
_t: string,
|
||||||
|
id: string,
|
||||||
|
limit_kind: "percent" | "hardcap",
|
||||||
|
limit_value: number,
|
||||||
|
) {
|
||||||
|
const k = this.keys.find((x) => x.id === id);
|
||||||
|
if (k) {
|
||||||
|
k.limit_kind = limit_kind;
|
||||||
|
k.limit_value = limit_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async redeem(_t: string, code: string): Promise<AccountBalance> {
|
||||||
|
if (!code.startsWith("helexa-topup-")) {
|
||||||
|
throw new ApiError(400, "bad_request", "invalid or already-redeemed code");
|
||||||
|
}
|
||||||
|
this.total += 500_000;
|
||||||
|
return this.account();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance: AccountApi | null = null;
|
||||||
|
export function accountApi(): AccountApi {
|
||||||
|
if (!instance) {
|
||||||
|
instance = import.meta.env.VITE_USE_MOCK_ACCOUNT_API
|
||||||
|
? new MockAccountApi()
|
||||||
|
: new RealAccountApi();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
45
helexa.ai/src/api/types.ts
Normal file
45
helexa.ai/src/api/types.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Wire types for the helexa-upstream /web/v1 account API (B4/B5).
|
||||||
|
|
||||||
|
export interface ApiKeySummary {
|
||||||
|
id: string;
|
||||||
|
prefix: string;
|
||||||
|
label: string;
|
||||||
|
status: "active" | "archived";
|
||||||
|
limit_kind: "percent" | "hardcap";
|
||||||
|
limit_value: number;
|
||||||
|
spent: number;
|
||||||
|
reserved: number;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatedKey {
|
||||||
|
id: string;
|
||||||
|
/** Raw secret — shown exactly once at creation. */
|
||||||
|
key: string;
|
||||||
|
prefix: string;
|
||||||
|
limit_kind: "percent" | "hardcap";
|
||||||
|
limit_value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccountBalance {
|
||||||
|
account_id: string;
|
||||||
|
allocation_total: number;
|
||||||
|
allocation_spent: number;
|
||||||
|
allocation_reserved: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Session {
|
||||||
|
token: string;
|
||||||
|
expires_in: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Typed error carrying the backend's machine-readable code. */
|
||||||
|
export class ApiError extends Error {
|
||||||
|
code: string;
|
||||||
|
status: number;
|
||||||
|
constructor(status: number, code: string, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
helexa.ai/src/auth/AuthProvider.tsx
Normal file
53
helexa.ai/src/auth/AuthProvider.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
import { accountApi } from "../api/account";
|
||||||
|
import { claimAnonymousData } from "../data/repositories";
|
||||||
|
import { getFingerprint } from "../lib/fingerprint";
|
||||||
|
import { AuthContext } from "./context";
|
||||||
|
|
||||||
|
const TOKEN_KEY = "helexa.token";
|
||||||
|
const EMAIL_KEY = "helexa.email";
|
||||||
|
|
||||||
|
export default function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [token, setToken] = useState<string | null>(() =>
|
||||||
|
localStorage.getItem(TOKEN_KEY),
|
||||||
|
);
|
||||||
|
const [email, setEmail] = useState<string | null>(() =>
|
||||||
|
localStorage.getItem(EMAIL_KEY),
|
||||||
|
);
|
||||||
|
|
||||||
|
async function login(em: string, password: string): Promise<void> {
|
||||||
|
const api = accountApi();
|
||||||
|
const session = await api.login(em, password);
|
||||||
|
localStorage.setItem(TOKEN_KEY, session.token);
|
||||||
|
localStorage.setItem(EMAIL_KEY, em);
|
||||||
|
setToken(session.token);
|
||||||
|
setEmail(em);
|
||||||
|
// Claim anonymous local history into the account (stays client-side).
|
||||||
|
try {
|
||||||
|
const acct = await api.account(session.token);
|
||||||
|
await claimAnonymousData(acct.account_id);
|
||||||
|
} catch {
|
||||||
|
/* non-fatal */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(em: string, password: string): Promise<void> {
|
||||||
|
const fingerprint = await getFingerprint();
|
||||||
|
await accountApi().register(em, password, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout(): void {
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
localStorage.removeItem(EMAIL_KEY);
|
||||||
|
setToken(null);
|
||||||
|
setEmail(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{ token, email, status: token ? "authed" : "anon", login, register, logout }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
helexa.ai/src/auth/RequireAuth.tsx
Normal file
14
helexa.ai/src/auth/RequireAuth.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { type ReactNode } from "react";
|
||||||
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
|
import { useAuth } from "./context";
|
||||||
|
|
||||||
|
/** Route guard: redirect unauthenticated users to /login?next=…. */
|
||||||
|
export default function RequireAuth({ children }: { children: ReactNode }) {
|
||||||
|
const { status } = useAuth();
|
||||||
|
const location = useLocation();
|
||||||
|
if (status !== "authed") {
|
||||||
|
const next = encodeURIComponent(location.pathname + location.search);
|
||||||
|
return <Navigate to={`/login?next=${next}`} replace />;
|
||||||
|
}
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
23
helexa.ai/src/auth/context.ts
Normal file
23
helexa.ai/src/auth/context.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
export interface AuthContextValue {
|
||||||
|
token: string | null;
|
||||||
|
email: string | null;
|
||||||
|
status: "anon" | "authed";
|
||||||
|
login: (email: string, password: string) => Promise<void>;
|
||||||
|
register: (email: string, password: string) => Promise<void>;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthContext = createContext<AuthContextValue>({
|
||||||
|
token: null,
|
||||||
|
email: null,
|
||||||
|
status: "anon",
|
||||||
|
login: async () => {},
|
||||||
|
register: async () => {},
|
||||||
|
logout: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useAuth(): AuthContextValue {
|
||||||
|
return useContext(AuthContext);
|
||||||
|
}
|
||||||
@@ -6,11 +6,12 @@ import { useTheme } from "../layout/theme";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AUTONYM_MAP, type LanguageCode, isRtlLanguage } from "../i18n/languages";
|
import { AUTONYM_MAP, type LanguageCode, isRtlLanguage } from "../i18n/languages";
|
||||||
import { getLanguageOptionsByUsage } from "../i18n/translation-priority";
|
import { getLanguageOptionsByUsage } from "../i18n/translation-priority";
|
||||||
|
import { useAuth } from "../auth/context";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top navigation: brand, primary routes (chat at `/`, `/mission`), an
|
* Top navigation: brand, primary routes (chat at `/`, `/mission`), an
|
||||||
* auth-aware cluster (stubbed until F4 wires sessions), the theme toggle,
|
* auth-aware cluster (Account/Sign out when signed in, else Sign in/up),
|
||||||
* and the language selector.
|
* the theme toggle, and the language selector.
|
||||||
*
|
*
|
||||||
* The language picker is ordered by **estimated usage**
|
* The language picker is ordered by **estimated usage**
|
||||||
* (getLanguageOptionsByUsage), not alphabetically — a deliberate choice that
|
* (getLanguageOptionsByUsage), not alphabetically — a deliberate choice that
|
||||||
@@ -21,6 +22,7 @@ import { getLanguageOptionsByUsage } from "../i18n/translation-priority";
|
|||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
const { t, i18n } = useTranslation("common");
|
const { t, i18n } = useTranslation("common");
|
||||||
|
const { status, logout } = useAuth();
|
||||||
|
|
||||||
const currentLanguage: LanguageCode = (i18n.language.split("-")[0] ||
|
const currentLanguage: LanguageCode = (i18n.language.split("-")[0] ||
|
||||||
"en") as LanguageCode;
|
"en") as LanguageCode;
|
||||||
@@ -75,13 +77,31 @@ const Header: React.FC = () => {
|
|||||||
</Nav>
|
</Nav>
|
||||||
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
<div className="d-flex align-items-center gap-2">
|
||||||
{/* Auth cluster — plain links until F4 wires session state. */}
|
{/* Auth-aware cluster. */}
|
||||||
<NavLink to="/login" className="nav-link">
|
{status === "authed" ? (
|
||||||
{t("nav.login")}
|
<>
|
||||||
</NavLink>
|
<NavLink to="/account" className="nav-link">
|
||||||
<NavLink to="/register" className="nav-link">
|
{t("nav.account")}
|
||||||
{t("nav.register")}
|
</NavLink>
|
||||||
</NavLink>
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline-secondary"
|
||||||
|
onClick={logout}
|
||||||
|
className="me-1"
|
||||||
|
>
|
||||||
|
{t("nav.logout")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<NavLink to="/login" className="nav-link">
|
||||||
|
{t("nav.login")}
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/register" className="nav-link">
|
||||||
|
{t("nav.register")}
|
||||||
|
</NavLink>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -13,131 +13,163 @@ import ruCommon from "./resources/ru/common.json";
|
|||||||
import enMission from "./resources/en/mission.json";
|
import enMission from "./resources/en/mission.json";
|
||||||
import ruMission from "./resources/ru/mission.json";
|
import ruMission from "./resources/ru/mission.json";
|
||||||
import enChat from "./resources/en/chat.json";
|
import enChat from "./resources/en/chat.json";
|
||||||
|
import enAccount from "./resources/en/account.json";
|
||||||
import ruChat from "./resources/ru/chat.json";
|
import ruChat from "./resources/ru/chat.json";
|
||||||
|
import ruAccount from "./resources/ru/account.json";
|
||||||
|
|
||||||
// Scandinavian & Nordic languages
|
// Scandinavian & Nordic languages
|
||||||
import daCommon from "./resources/da/common.json";
|
import daCommon from "./resources/da/common.json";
|
||||||
import daMission from "./resources/da/mission.json";
|
import daMission from "./resources/da/mission.json";
|
||||||
import daChat from "./resources/da/chat.json";
|
import daChat from "./resources/da/chat.json";
|
||||||
|
import daAccount from "./resources/da/account.json";
|
||||||
|
|
||||||
import fiCommon from "./resources/fi/common.json";
|
import fiCommon from "./resources/fi/common.json";
|
||||||
import fiMission from "./resources/fi/mission.json";
|
import fiMission from "./resources/fi/mission.json";
|
||||||
import fiChat from "./resources/fi/chat.json";
|
import fiChat from "./resources/fi/chat.json";
|
||||||
|
import fiAccount from "./resources/fi/account.json";
|
||||||
|
|
||||||
import noCommon from "./resources/no/common.json";
|
import noCommon from "./resources/no/common.json";
|
||||||
import noMission from "./resources/no/mission.json";
|
import noMission from "./resources/no/mission.json";
|
||||||
import noChat from "./resources/no/chat.json";
|
import noChat from "./resources/no/chat.json";
|
||||||
|
import noAccount from "./resources/no/account.json";
|
||||||
|
|
||||||
import svCommon from "./resources/sv/common.json";
|
import svCommon from "./resources/sv/common.json";
|
||||||
import svMission from "./resources/sv/mission.json";
|
import svMission from "./resources/sv/mission.json";
|
||||||
import svChat from "./resources/sv/chat.json";
|
import svChat from "./resources/sv/chat.json";
|
||||||
|
import svAccount from "./resources/sv/account.json";
|
||||||
|
|
||||||
import bgCommon from "./resources/bg/common.json";
|
import bgCommon from "./resources/bg/common.json";
|
||||||
import bgMission from "./resources/bg/mission.json";
|
import bgMission from "./resources/bg/mission.json";
|
||||||
import bgChat from "./resources/bg/chat.json";
|
import bgChat from "./resources/bg/chat.json";
|
||||||
|
import bgAccount from "./resources/bg/account.json";
|
||||||
|
|
||||||
import etCommon from "./resources/et/common.json";
|
import etCommon from "./resources/et/common.json";
|
||||||
import etMission from "./resources/et/mission.json";
|
import etMission from "./resources/et/mission.json";
|
||||||
import etChat from "./resources/et/chat.json";
|
import etChat from "./resources/et/chat.json";
|
||||||
|
import etAccount from "./resources/et/account.json";
|
||||||
|
|
||||||
// African & MENA languages
|
// African & MENA languages
|
||||||
import swCommon from "./resources/sw/common.json";
|
import swCommon from "./resources/sw/common.json";
|
||||||
import swMission from "./resources/sw/mission.json";
|
import swMission from "./resources/sw/mission.json";
|
||||||
import swChat from "./resources/sw/chat.json";
|
import swChat from "./resources/sw/chat.json";
|
||||||
|
import swAccount from "./resources/sw/account.json";
|
||||||
|
|
||||||
import arCommon from "./resources/ar/common.json";
|
import arCommon from "./resources/ar/common.json";
|
||||||
import arMission from "./resources/ar/mission.json";
|
import arMission from "./resources/ar/mission.json";
|
||||||
import arChat from "./resources/ar/chat.json";
|
import arChat from "./resources/ar/chat.json";
|
||||||
|
import arAccount from "./resources/ar/account.json";
|
||||||
|
|
||||||
import faCommon from "./resources/fa/common.json";
|
import faCommon from "./resources/fa/common.json";
|
||||||
import faMission from "./resources/fa/mission.json";
|
import faMission from "./resources/fa/mission.json";
|
||||||
import faChat from "./resources/fa/chat.json";
|
import faChat from "./resources/fa/chat.json";
|
||||||
|
import faAccount from "./resources/fa/account.json";
|
||||||
|
|
||||||
import haCommon from "./resources/ha/common.json";
|
import haCommon from "./resources/ha/common.json";
|
||||||
import haMission from "./resources/ha/mission.json";
|
import haMission from "./resources/ha/mission.json";
|
||||||
import haChat from "./resources/ha/chat.json";
|
import haChat from "./resources/ha/chat.json";
|
||||||
|
import haAccount from "./resources/ha/account.json";
|
||||||
|
|
||||||
import amCommon from "./resources/am/common.json";
|
import amCommon from "./resources/am/common.json";
|
||||||
import amMission from "./resources/am/mission.json";
|
import amMission from "./resources/am/mission.json";
|
||||||
import amChat from "./resources/am/chat.json";
|
import amChat from "./resources/am/chat.json";
|
||||||
|
import amAccount from "./resources/am/account.json";
|
||||||
|
|
||||||
import yoCommon from "./resources/yo/common.json";
|
import yoCommon from "./resources/yo/common.json";
|
||||||
import yoMission from "./resources/yo/mission.json";
|
import yoMission from "./resources/yo/mission.json";
|
||||||
import yoChat from "./resources/yo/chat.json";
|
import yoChat from "./resources/yo/chat.json";
|
||||||
|
import yoAccount from "./resources/yo/account.json";
|
||||||
|
|
||||||
import zuCommon from "./resources/zu/common.json";
|
import zuCommon from "./resources/zu/common.json";
|
||||||
import zuMission from "./resources/zu/mission.json";
|
import zuMission from "./resources/zu/mission.json";
|
||||||
import zuChat from "./resources/zu/chat.json";
|
import zuChat from "./resources/zu/chat.json";
|
||||||
|
import zuAccount from "./resources/zu/account.json";
|
||||||
|
|
||||||
// Darija (Moroccan Arabic)
|
// Darija (Moroccan Arabic)
|
||||||
import maCommon from "./resources/ma/common.json";
|
import maCommon from "./resources/ma/common.json";
|
||||||
import maMission from "./resources/ma/mission.json";
|
import maMission from "./resources/ma/mission.json";
|
||||||
import maChat from "./resources/ma/chat.json";
|
import maChat from "./resources/ma/chat.json";
|
||||||
|
import maAccount from "./resources/ma/account.json";
|
||||||
|
|
||||||
// European / other languages
|
// European / other languages
|
||||||
import esCommon from "./resources/es/common.json";
|
import esCommon from "./resources/es/common.json";
|
||||||
import esMission from "./resources/es/mission.json";
|
import esMission from "./resources/es/mission.json";
|
||||||
import esChat from "./resources/es/chat.json";
|
import esChat from "./resources/es/chat.json";
|
||||||
|
import esAccount from "./resources/es/account.json";
|
||||||
|
|
||||||
import frCommon from "./resources/fr/common.json";
|
import frCommon from "./resources/fr/common.json";
|
||||||
import frMission from "./resources/fr/mission.json";
|
import frMission from "./resources/fr/mission.json";
|
||||||
import frChat from "./resources/fr/chat.json";
|
import frChat from "./resources/fr/chat.json";
|
||||||
|
import frAccount from "./resources/fr/account.json";
|
||||||
|
|
||||||
import deCommon from "./resources/de/common.json";
|
import deCommon from "./resources/de/common.json";
|
||||||
import deMission from "./resources/de/mission.json";
|
import deMission from "./resources/de/mission.json";
|
||||||
import deChat from "./resources/de/chat.json";
|
import deChat from "./resources/de/chat.json";
|
||||||
|
import deAccount from "./resources/de/account.json";
|
||||||
|
|
||||||
import elCommon from "./resources/el/common.json";
|
import elCommon from "./resources/el/common.json";
|
||||||
import elMission from "./resources/el/mission.json";
|
import elMission from "./resources/el/mission.json";
|
||||||
import elChat from "./resources/el/chat.json";
|
import elChat from "./resources/el/chat.json";
|
||||||
|
import elAccount from "./resources/el/account.json";
|
||||||
|
|
||||||
import itCommon from "./resources/it/common.json";
|
import itCommon from "./resources/it/common.json";
|
||||||
import itMission from "./resources/it/mission.json";
|
import itMission from "./resources/it/mission.json";
|
||||||
import itChat from "./resources/it/chat.json";
|
import itChat from "./resources/it/chat.json";
|
||||||
|
import itAccount from "./resources/it/account.json";
|
||||||
|
|
||||||
import heCommon from "./resources/he/common.json";
|
import heCommon from "./resources/he/common.json";
|
||||||
import heMission from "./resources/he/mission.json";
|
import heMission from "./resources/he/mission.json";
|
||||||
import heChat from "./resources/he/chat.json";
|
import heChat from "./resources/he/chat.json";
|
||||||
|
import heAccount from "./resources/he/account.json";
|
||||||
|
|
||||||
import ptCommon from "./resources/pt/common.json";
|
import ptCommon from "./resources/pt/common.json";
|
||||||
import ptMission from "./resources/pt/mission.json";
|
import ptMission from "./resources/pt/mission.json";
|
||||||
import ptChat from "./resources/pt/chat.json";
|
import ptChat from "./resources/pt/chat.json";
|
||||||
|
import ptAccount from "./resources/pt/account.json";
|
||||||
|
|
||||||
import roCommon from "./resources/ro/common.json";
|
import roCommon from "./resources/ro/common.json";
|
||||||
import roMission from "./resources/ro/mission.json";
|
import roMission from "./resources/ro/mission.json";
|
||||||
import roChat from "./resources/ro/chat.json";
|
import roChat from "./resources/ro/chat.json";
|
||||||
|
import roAccount from "./resources/ro/account.json";
|
||||||
|
|
||||||
import kaCommon from "./resources/ka/common.json";
|
import kaCommon from "./resources/ka/common.json";
|
||||||
import kaMission from "./resources/ka/mission.json";
|
import kaMission from "./resources/ka/mission.json";
|
||||||
import kaChat from "./resources/ka/chat.json";
|
import kaChat from "./resources/ka/chat.json";
|
||||||
|
import kaAccount from "./resources/ka/account.json";
|
||||||
|
|
||||||
import trCommon from "./resources/tr/common.json";
|
import trCommon from "./resources/tr/common.json";
|
||||||
import trMission from "./resources/tr/mission.json";
|
import trMission from "./resources/tr/mission.json";
|
||||||
import trChat from "./resources/tr/chat.json";
|
import trChat from "./resources/tr/chat.json";
|
||||||
|
import trAccount from "./resources/tr/account.json";
|
||||||
|
|
||||||
import plCommon from "./resources/pl/common.json";
|
import plCommon from "./resources/pl/common.json";
|
||||||
import plMission from "./resources/pl/mission.json";
|
import plMission from "./resources/pl/mission.json";
|
||||||
import plChat from "./resources/pl/chat.json";
|
import plChat from "./resources/pl/chat.json";
|
||||||
|
import plAccount from "./resources/pl/account.json";
|
||||||
|
|
||||||
import ukCommon from "./resources/uk/common.json";
|
import ukCommon from "./resources/uk/common.json";
|
||||||
import ukMission from "./resources/uk/mission.json";
|
import ukMission from "./resources/uk/mission.json";
|
||||||
import ukChat from "./resources/uk/chat.json";
|
import ukChat from "./resources/uk/chat.json";
|
||||||
|
import ukAccount from "./resources/uk/account.json";
|
||||||
|
|
||||||
import nlCommon from "./resources/nl/common.json";
|
import nlCommon from "./resources/nl/common.json";
|
||||||
import nlMission from "./resources/nl/mission.json";
|
import nlMission from "./resources/nl/mission.json";
|
||||||
import nlChat from "./resources/nl/chat.json";
|
import nlChat from "./resources/nl/chat.json";
|
||||||
|
import nlAccount from "./resources/nl/account.json";
|
||||||
|
|
||||||
import srCommon from "./resources/sr/common.json";
|
import srCommon from "./resources/sr/common.json";
|
||||||
import srMission from "./resources/sr/mission.json";
|
import srMission from "./resources/sr/mission.json";
|
||||||
import srChat from "./resources/sr/chat.json";
|
import srChat from "./resources/sr/chat.json";
|
||||||
|
import srAccount from "./resources/sr/account.json";
|
||||||
|
|
||||||
import kkCommon from "./resources/kk/common.json";
|
import kkCommon from "./resources/kk/common.json";
|
||||||
import kkMission from "./resources/kk/mission.json";
|
import kkMission from "./resources/kk/mission.json";
|
||||||
import kkChat from "./resources/kk/chat.json";
|
import kkChat from "./resources/kk/chat.json";
|
||||||
|
import kkAccount from "./resources/kk/account.json";
|
||||||
|
|
||||||
import uzCommon from "./resources/uz/common.json";
|
import uzCommon from "./resources/uz/common.json";
|
||||||
import uzMission from "./resources/uz/mission.json";
|
import uzMission from "./resources/uz/mission.json";
|
||||||
import uzChat from "./resources/uz/chat.json";
|
import uzChat from "./resources/uz/chat.json";
|
||||||
|
import uzAccount from "./resources/uz/account.json";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application translation resources, split by language and namespace.
|
* Application translation resources, split by language and namespace.
|
||||||
@@ -151,41 +183,49 @@ const resources: Resource = {
|
|||||||
common: enCommon,
|
common: enCommon,
|
||||||
mission: enMission,
|
mission: enMission,
|
||||||
chat: enChat,
|
chat: enChat,
|
||||||
|
account: enAccount,
|
||||||
},
|
},
|
||||||
ru: {
|
ru: {
|
||||||
common: ruCommon,
|
common: ruCommon,
|
||||||
mission: ruMission,
|
mission: ruMission,
|
||||||
chat: ruChat,
|
chat: ruChat,
|
||||||
|
account: ruAccount,
|
||||||
},
|
},
|
||||||
bg: {
|
bg: {
|
||||||
common: bgCommon,
|
common: bgCommon,
|
||||||
mission: bgMission,
|
mission: bgMission,
|
||||||
chat: bgChat,
|
chat: bgChat,
|
||||||
|
account: bgAccount,
|
||||||
},
|
},
|
||||||
da: {
|
da: {
|
||||||
common: daCommon,
|
common: daCommon,
|
||||||
mission: daMission,
|
mission: daMission,
|
||||||
chat: daChat,
|
chat: daChat,
|
||||||
|
account: daAccount,
|
||||||
},
|
},
|
||||||
et: {
|
et: {
|
||||||
common: etCommon,
|
common: etCommon,
|
||||||
mission: etMission,
|
mission: etMission,
|
||||||
chat: etChat,
|
chat: etChat,
|
||||||
|
account: etAccount,
|
||||||
},
|
},
|
||||||
fi: {
|
fi: {
|
||||||
common: fiCommon,
|
common: fiCommon,
|
||||||
mission: fiMission,
|
mission: fiMission,
|
||||||
chat: fiChat,
|
chat: fiChat,
|
||||||
|
account: fiAccount,
|
||||||
},
|
},
|
||||||
kk: {
|
kk: {
|
||||||
common: kkCommon,
|
common: kkCommon,
|
||||||
mission: kkMission,
|
mission: kkMission,
|
||||||
chat: kkChat,
|
chat: kkChat,
|
||||||
|
account: kkAccount,
|
||||||
},
|
},
|
||||||
uz: {
|
uz: {
|
||||||
common: uzCommon,
|
common: uzCommon,
|
||||||
mission: uzMission,
|
mission: uzMission,
|
||||||
chat: uzChat,
|
chat: uzChat,
|
||||||
|
account: uzAccount,
|
||||||
},
|
},
|
||||||
|
|
||||||
// African & MENA languages (LTR unless marked RTL via isRtlLanguage)
|
// African & MENA languages (LTR unless marked RTL via isRtlLanguage)
|
||||||
@@ -193,41 +233,49 @@ const resources: Resource = {
|
|||||||
common: swCommon,
|
common: swCommon,
|
||||||
mission: swMission,
|
mission: swMission,
|
||||||
chat: swChat,
|
chat: swChat,
|
||||||
|
account: swAccount,
|
||||||
},
|
},
|
||||||
ar: {
|
ar: {
|
||||||
common: arCommon,
|
common: arCommon,
|
||||||
mission: arMission,
|
mission: arMission,
|
||||||
chat: arChat,
|
chat: arChat,
|
||||||
|
account: arAccount,
|
||||||
},
|
},
|
||||||
fa: {
|
fa: {
|
||||||
common: faCommon,
|
common: faCommon,
|
||||||
mission: faMission,
|
mission: faMission,
|
||||||
chat: faChat,
|
chat: faChat,
|
||||||
|
account: faAccount,
|
||||||
},
|
},
|
||||||
ha: {
|
ha: {
|
||||||
common: haCommon,
|
common: haCommon,
|
||||||
mission: haMission,
|
mission: haMission,
|
||||||
chat: haChat,
|
chat: haChat,
|
||||||
|
account: haAccount,
|
||||||
},
|
},
|
||||||
am: {
|
am: {
|
||||||
common: amCommon,
|
common: amCommon,
|
||||||
mission: amMission,
|
mission: amMission,
|
||||||
chat: amChat,
|
chat: amChat,
|
||||||
|
account: amAccount,
|
||||||
},
|
},
|
||||||
yo: {
|
yo: {
|
||||||
common: yoCommon,
|
common: yoCommon,
|
||||||
mission: yoMission,
|
mission: yoMission,
|
||||||
chat: yoChat,
|
chat: yoChat,
|
||||||
|
account: yoAccount,
|
||||||
},
|
},
|
||||||
zu: {
|
zu: {
|
||||||
common: zuCommon,
|
common: zuCommon,
|
||||||
mission: zuMission,
|
mission: zuMission,
|
||||||
chat: zuChat,
|
chat: zuChat,
|
||||||
|
account: zuAccount,
|
||||||
},
|
},
|
||||||
ma: {
|
ma: {
|
||||||
common: maCommon,
|
common: maCommon,
|
||||||
mission: maMission,
|
mission: maMission,
|
||||||
chat: maChat,
|
chat: maChat,
|
||||||
|
account: maAccount,
|
||||||
},
|
},
|
||||||
|
|
||||||
// European & other languages
|
// European & other languages
|
||||||
@@ -235,81 +283,97 @@ const resources: Resource = {
|
|||||||
common: esCommon,
|
common: esCommon,
|
||||||
mission: esMission,
|
mission: esMission,
|
||||||
chat: esChat,
|
chat: esChat,
|
||||||
|
account: esAccount,
|
||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
common: frCommon,
|
common: frCommon,
|
||||||
mission: frMission,
|
mission: frMission,
|
||||||
chat: frChat,
|
chat: frChat,
|
||||||
|
account: frAccount,
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
common: deCommon,
|
common: deCommon,
|
||||||
mission: deMission,
|
mission: deMission,
|
||||||
chat: deChat,
|
chat: deChat,
|
||||||
|
account: deAccount,
|
||||||
},
|
},
|
||||||
el: {
|
el: {
|
||||||
common: elCommon,
|
common: elCommon,
|
||||||
mission: elMission,
|
mission: elMission,
|
||||||
chat: elChat,
|
chat: elChat,
|
||||||
|
account: elAccount,
|
||||||
},
|
},
|
||||||
it: {
|
it: {
|
||||||
common: itCommon,
|
common: itCommon,
|
||||||
mission: itMission,
|
mission: itMission,
|
||||||
chat: itChat,
|
chat: itChat,
|
||||||
|
account: itAccount,
|
||||||
},
|
},
|
||||||
he: {
|
he: {
|
||||||
common: heCommon,
|
common: heCommon,
|
||||||
mission: heMission,
|
mission: heMission,
|
||||||
chat: heChat,
|
chat: heChat,
|
||||||
|
account: heAccount,
|
||||||
},
|
},
|
||||||
pt: {
|
pt: {
|
||||||
common: ptCommon,
|
common: ptCommon,
|
||||||
mission: ptMission,
|
mission: ptMission,
|
||||||
chat: ptChat,
|
chat: ptChat,
|
||||||
|
account: ptAccount,
|
||||||
},
|
},
|
||||||
ro: {
|
ro: {
|
||||||
common: roCommon,
|
common: roCommon,
|
||||||
mission: roMission,
|
mission: roMission,
|
||||||
chat: roChat,
|
chat: roChat,
|
||||||
|
account: roAccount,
|
||||||
},
|
},
|
||||||
ka: {
|
ka: {
|
||||||
common: kaCommon,
|
common: kaCommon,
|
||||||
mission: kaMission,
|
mission: kaMission,
|
||||||
chat: kaChat,
|
chat: kaChat,
|
||||||
|
account: kaAccount,
|
||||||
},
|
},
|
||||||
tr: {
|
tr: {
|
||||||
common: trCommon,
|
common: trCommon,
|
||||||
mission: trMission,
|
mission: trMission,
|
||||||
chat: trChat,
|
chat: trChat,
|
||||||
|
account: trAccount,
|
||||||
},
|
},
|
||||||
pl: {
|
pl: {
|
||||||
common: plCommon,
|
common: plCommon,
|
||||||
mission: plMission,
|
mission: plMission,
|
||||||
chat: plChat,
|
chat: plChat,
|
||||||
|
account: plAccount,
|
||||||
},
|
},
|
||||||
uk: {
|
uk: {
|
||||||
common: ukCommon,
|
common: ukCommon,
|
||||||
mission: ukMission,
|
mission: ukMission,
|
||||||
chat: ukChat,
|
chat: ukChat,
|
||||||
|
account: ukAccount,
|
||||||
},
|
},
|
||||||
nl: {
|
nl: {
|
||||||
common: nlCommon,
|
common: nlCommon,
|
||||||
mission: nlMission,
|
mission: nlMission,
|
||||||
chat: nlChat,
|
chat: nlChat,
|
||||||
|
account: nlAccount,
|
||||||
},
|
},
|
||||||
sr: {
|
sr: {
|
||||||
common: srCommon,
|
common: srCommon,
|
||||||
mission: srMission,
|
mission: srMission,
|
||||||
chat: srChat,
|
chat: srChat,
|
||||||
|
account: srAccount,
|
||||||
},
|
},
|
||||||
no: {
|
no: {
|
||||||
common: noCommon,
|
common: noCommon,
|
||||||
mission: noMission,
|
mission: noMission,
|
||||||
chat: noChat,
|
chat: noChat,
|
||||||
|
account: noAccount,
|
||||||
},
|
},
|
||||||
sv: {
|
sv: {
|
||||||
common: svCommon,
|
common: svCommon,
|
||||||
mission: svMission,
|
mission: svMission,
|
||||||
chat: svChat,
|
chat: svChat,
|
||||||
|
account: svAccount,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -335,7 +399,7 @@ i18n.use(initReactI18next).init({
|
|||||||
lng: browserLang,
|
lng: browserLang,
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
supportedLngs: SUPPORTED_LANGUAGES,
|
supportedLngs: SUPPORTED_LANGUAGES,
|
||||||
ns: ["common", "mission", "chat"],
|
ns: ["common", "mission", "chat", "account"],
|
||||||
defaultNS: "common",
|
defaultNS: "common",
|
||||||
// Because we control the keys and interpolate only simple values.
|
// Because we control the keys and interpolate only simple values.
|
||||||
interpolation: {
|
interpolation: {
|
||||||
|
|||||||
69
helexa.ai/src/i18n/resources/am/account.json
Normal file
69
helexa.ai/src/i18n/resources/am/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ar/account.json
Normal file
69
helexa.ai/src/i18n/resources/ar/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/bg/account.json
Normal file
69
helexa.ai/src/i18n/resources/bg/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/da/account.json
Normal file
69
helexa.ai/src/i18n/resources/da/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/de/account.json
Normal file
69
helexa.ai/src/i18n/resources/de/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/el/account.json
Normal file
69
helexa.ai/src/i18n/resources/el/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/en/account.json
Normal file
69
helexa.ai/src/i18n/resources/en/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/es/account.json
Normal file
69
helexa.ai/src/i18n/resources/es/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/et/account.json
Normal file
69
helexa.ai/src/i18n/resources/et/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/fa/account.json
Normal file
69
helexa.ai/src/i18n/resources/fa/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/fi/account.json
Normal file
69
helexa.ai/src/i18n/resources/fi/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/fr/account.json
Normal file
69
helexa.ai/src/i18n/resources/fr/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ha/account.json
Normal file
69
helexa.ai/src/i18n/resources/ha/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/he/account.json
Normal file
69
helexa.ai/src/i18n/resources/he/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/it/account.json
Normal file
69
helexa.ai/src/i18n/resources/it/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ka/account.json
Normal file
69
helexa.ai/src/i18n/resources/ka/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/kk/account.json
Normal file
69
helexa.ai/src/i18n/resources/kk/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ma/account.json
Normal file
69
helexa.ai/src/i18n/resources/ma/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/nl/account.json
Normal file
69
helexa.ai/src/i18n/resources/nl/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/no/account.json
Normal file
69
helexa.ai/src/i18n/resources/no/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/pl/account.json
Normal file
69
helexa.ai/src/i18n/resources/pl/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/pt/account.json
Normal file
69
helexa.ai/src/i18n/resources/pt/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ro/account.json
Normal file
69
helexa.ai/src/i18n/resources/ro/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/ru/account.json
Normal file
69
helexa.ai/src/i18n/resources/ru/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/sr/account.json
Normal file
69
helexa.ai/src/i18n/resources/sr/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/sv/account.json
Normal file
69
helexa.ai/src/i18n/resources/sv/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/sw/account.json
Normal file
69
helexa.ai/src/i18n/resources/sw/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/tr/account.json
Normal file
69
helexa.ai/src/i18n/resources/tr/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/uk/account.json
Normal file
69
helexa.ai/src/i18n/resources/uk/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/uz/account.json
Normal file
69
helexa.ai/src/i18n/resources/uz/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/yo/account.json
Normal file
69
helexa.ai/src/i18n/resources/yo/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
69
helexa.ai/src/i18n/resources/zu/account.json
Normal file
69
helexa.ai/src/i18n/resources/zu/account.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"login": {
|
||||||
|
"title": "Sign in",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign in",
|
||||||
|
"noAccount": "No account? Sign up"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"title": "Create your account",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"submit": "Sign up",
|
||||||
|
"haveAccount": "Already have an account? Sign in",
|
||||||
|
"checkEmail": "Almost there — check your email to verify your account."
|
||||||
|
},
|
||||||
|
"verify": {
|
||||||
|
"verifying": "Verifying…",
|
||||||
|
"ok": "Email verified. You can now sign in.",
|
||||||
|
"failed": "This verification link is invalid or has expired.",
|
||||||
|
"toLogin": "Go to sign in"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"requestTitle": "Reset your password",
|
||||||
|
"email": "Email",
|
||||||
|
"requestSubmit": "Send reset link",
|
||||||
|
"requestDone": "If that email has an account, a reset link is on its way.",
|
||||||
|
"confirmTitle": "Choose a new password",
|
||||||
|
"newPassword": "New password",
|
||||||
|
"confirmSubmit": "Set password",
|
||||||
|
"ok": "Password updated. You can now sign in."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Account",
|
||||||
|
"balance": "Allocation",
|
||||||
|
"total": "Total",
|
||||||
|
"spent": "Spent",
|
||||||
|
"reserved": "Reserved",
|
||||||
|
"remaining": "Remaining",
|
||||||
|
"manageKeys": "Manage API keys",
|
||||||
|
"redeemTitle": "Redeem a top-up code",
|
||||||
|
"redeemPlaceholder": "helexa-topup-…",
|
||||||
|
"redeem": "Redeem",
|
||||||
|
"redeemed": "Code redeemed.",
|
||||||
|
"logout": "Sign out"
|
||||||
|
},
|
||||||
|
"keys": {
|
||||||
|
"title": "API keys",
|
||||||
|
"create": "Create key",
|
||||||
|
"label": "Label",
|
||||||
|
"limitKind": "Limit",
|
||||||
|
"percent": "% of allocation",
|
||||||
|
"hardcap": "Hard cap (tokens)",
|
||||||
|
"value": "Value",
|
||||||
|
"none": "No keys yet.",
|
||||||
|
"createdTitle": "Your new API key",
|
||||||
|
"createdWarn": "Copy it now — you won't see it again.",
|
||||||
|
"copy": "Copy",
|
||||||
|
"copied": "Copied",
|
||||||
|
"archive": "Archive",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"usage": "Used"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"generic": "Something went wrong.",
|
||||||
|
"unauthorized": "Please sign in again."
|
||||||
|
}
|
||||||
|
}
|
||||||
159
helexa.ai/src/pages/account/ApiKeys.tsx
Normal file
159
helexa.ai/src/pages/account/ApiKeys.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { Alert, Badge, Button, Container, Form, Modal, Table } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAuth } from "../../auth/context";
|
||||||
|
import { accountApi } from "../../api/account";
|
||||||
|
import { ApiError, type ApiKeySummary, type CreatedKey } from "../../api/types";
|
||||||
|
|
||||||
|
type LimitKind = "percent" | "hardcap";
|
||||||
|
|
||||||
|
export default function ApiKeys() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const { token, logout } = useAuth();
|
||||||
|
const [keys, setKeys] = useState<ApiKeySummary[]>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Create-key form state.
|
||||||
|
const [label, setLabel] = useState("");
|
||||||
|
const [limitKind, setLimitKind] = useState<LimitKind>("percent");
|
||||||
|
const [limitValue, setLimitValue] = useState(100);
|
||||||
|
const [created, setCreated] = useState<CreatedKey | null>(null);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
if (!token) return;
|
||||||
|
try {
|
||||||
|
setKeys(await accountApi().listKeys(token));
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError && err.status === 401) logout();
|
||||||
|
else setError(t("error.generic"));
|
||||||
|
}
|
||||||
|
}, [token, logout, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// load() is async; setState happens after await, not synchronously.
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
void load();
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
async function create(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!token) return;
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const key = await accountApi().createKey(token, label, limitKind, limitValue);
|
||||||
|
setCreated(key);
|
||||||
|
setLabel("");
|
||||||
|
await load();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof ApiError ? err.message : t("error.generic"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function archive(id: string) {
|
||||||
|
if (!token) return;
|
||||||
|
await accountApi().archiveKey(token, id);
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 860 }}>
|
||||||
|
<h1 className="h3 mb-4">{t("keys.title")}</h1>
|
||||||
|
{error && <Alert variant="warning">{error}</Alert>}
|
||||||
|
|
||||||
|
<Form onSubmit={create} className="surface-elevated p-3 rounded-3 mb-4">
|
||||||
|
<div className="row g-2 align-items-end">
|
||||||
|
<div className="col">
|
||||||
|
<Form.Label className="small">{t("keys.label")}</Form.Label>
|
||||||
|
<Form.Control value={label} onChange={(e) => setLabel(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<Form.Label className="small">{t("keys.limitKind")}</Form.Label>
|
||||||
|
<Form.Select
|
||||||
|
value={limitKind}
|
||||||
|
onChange={(e) => setLimitKind(e.target.value as LimitKind)}
|
||||||
|
>
|
||||||
|
<option value="percent">{t("keys.percent")}</option>
|
||||||
|
<option value="hardcap">{t("keys.hardcap")}</option>
|
||||||
|
</Form.Select>
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<Form.Label className="small">{t("keys.value")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
value={limitValue}
|
||||||
|
onChange={(e) => setLimitValue(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">
|
||||||
|
<Button type="submit">{t("keys.create")}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{keys.length === 0 ? (
|
||||||
|
<p className="text-muted">{t("keys.none")}</p>
|
||||||
|
) : (
|
||||||
|
<Table responsive hover>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t("keys.label")}</th>
|
||||||
|
<th>Prefix</th>
|
||||||
|
<th>{t("keys.limitKind")}</th>
|
||||||
|
<th>{t("keys.usage")}</th>
|
||||||
|
<th>{t("keys.status")}</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{keys.map((k) => (
|
||||||
|
<tr key={k.id}>
|
||||||
|
<td>{k.label || "—"}</td>
|
||||||
|
<td>
|
||||||
|
<code>{k.prefix}…</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{k.limit_kind === "percent" ? `${k.limit_value}%` : k.limit_value.toLocaleString()}
|
||||||
|
</td>
|
||||||
|
<td>{k.spent.toLocaleString()}</td>
|
||||||
|
<td>
|
||||||
|
<Badge bg={k.status === "active" ? "success" : "secondary"}>{k.status}</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="text-end">
|
||||||
|
{k.status === "active" && (
|
||||||
|
<Button size="sm" variant="outline-danger" onClick={() => void archive(k.id)}>
|
||||||
|
{t("keys.archive")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* The raw key is shown exactly once. */}
|
||||||
|
<Modal show={!!created} onHide={() => setCreated(null)} centered>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title className="h6">{t("keys.createdTitle")}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Alert variant="warning" className="py-2">{t("keys.createdWarn")}</Alert>
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<Form.Control readOnly value={created?.key ?? ""} />
|
||||||
|
<Button
|
||||||
|
variant="outline-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
if (created) void navigator.clipboard.writeText(created.key);
|
||||||
|
setCopied(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copied ? t("keys.copied") : t("keys.copy")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
104
helexa.ai/src/pages/account/Dashboard.tsx
Normal file
104
helexa.ai/src/pages/account/Dashboard.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Alert, Button, Card, Container, Form, ProgressBar } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAuth } from "../../auth/context";
|
||||||
|
import { accountApi } from "../../api/account";
|
||||||
|
import { ApiError, type AccountBalance } from "../../api/types";
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const { token, logout } = useAuth();
|
||||||
|
const [balance, setBalance] = useState<AccountBalance | null>(null);
|
||||||
|
const [code, setCode] = useState("");
|
||||||
|
const [msg, setMsg] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
if (!token) return;
|
||||||
|
try {
|
||||||
|
setBalance(await accountApi().account(token));
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError && err.status === 401) logout();
|
||||||
|
else setError(t("error.generic"));
|
||||||
|
}
|
||||||
|
}, [token, logout, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// load() is async; setState happens after await, not synchronously.
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
void load();
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
async function redeem(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!token) return;
|
||||||
|
setError(null);
|
||||||
|
setMsg(null);
|
||||||
|
try {
|
||||||
|
setBalance(await accountApi().redeem(token, code.trim()));
|
||||||
|
setCode("");
|
||||||
|
setMsg(t("dashboard.redeemed"));
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof ApiError ? err.message : t("error.generic"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remaining = balance
|
||||||
|
? balance.allocation_total - balance.allocation_spent - balance.allocation_reserved
|
||||||
|
: 0;
|
||||||
|
const pct = balance && balance.allocation_total > 0
|
||||||
|
? Math.round(((balance.allocation_spent + balance.allocation_reserved) / balance.allocation_total) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 720 }}>
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 className="h3 mb-0">{t("dashboard.title")}</h1>
|
||||||
|
<Button variant="outline-secondary" size="sm" onClick={logout}>
|
||||||
|
{t("dashboard.logout")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="surface-elevated mb-4">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="h6 text-uppercase text-muted">
|
||||||
|
{t("dashboard.balance")}
|
||||||
|
</Card.Title>
|
||||||
|
{balance && (
|
||||||
|
<>
|
||||||
|
<ProgressBar now={pct} className="my-3" />
|
||||||
|
<div className="d-flex justify-content-between small">
|
||||||
|
<span>{t("dashboard.total")}: {balance.allocation_total.toLocaleString()}</span>
|
||||||
|
<span>{t("dashboard.spent")}: {balance.allocation_spent.toLocaleString()}</span>
|
||||||
|
<span>{t("dashboard.reserved")}: {balance.allocation_reserved.toLocaleString()}</span>
|
||||||
|
<span>{t("dashboard.remaining")}: {remaining.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Link to="/account/keys" className="btn btn-primary btn-sm mt-3">
|
||||||
|
{t("dashboard.manageKeys")}
|
||||||
|
</Link>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="surface-elevated">
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title className="h6">{t("dashboard.redeemTitle")}</Card.Title>
|
||||||
|
{msg && <Alert variant="success" className="py-2">{msg}</Alert>}
|
||||||
|
{error && <Alert variant="warning" className="py-2">{error}</Alert>}
|
||||||
|
<Form onSubmit={redeem} className="d-flex gap-2">
|
||||||
|
<Form.Control
|
||||||
|
value={code}
|
||||||
|
placeholder={t("dashboard.redeemPlaceholder")}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button type="submit" disabled={!code.trim()}>
|
||||||
|
{t("dashboard.redeem")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
64
helexa.ai/src/pages/auth/Login.tsx
Normal file
64
helexa.ai/src/pages/auth/Login.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { Alert, Button, Container, Form } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAuth } from "../../auth/context";
|
||||||
|
import { ApiError } from "../../api/types";
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const { login } = useAuth();
|
||||||
|
const nav = useNavigate();
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function submit(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setBusy(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await login(email, password);
|
||||||
|
nav(params.get("next") || "/account", { replace: true });
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof ApiError ? err.message : t("error.generic"));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 420 }}>
|
||||||
|
<h1 className="h3 mb-4">{t("login.title")}</h1>
|
||||||
|
{error && <Alert variant="warning">{error}</Alert>}
|
||||||
|
<Form onSubmit={submit}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("login.email")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("login.password")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button type="submit" disabled={busy} className="w-100">
|
||||||
|
{t("login.submit")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
<p className="mt-3 small">
|
||||||
|
<Link to="/register">{t("login.noAccount")}</Link>
|
||||||
|
</p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
70
helexa.ai/src/pages/auth/Register.tsx
Normal file
70
helexa.ai/src/pages/auth/Register.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Alert, Button, Container, Form } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAuth } from "../../auth/context";
|
||||||
|
import { ApiError } from "../../api/types";
|
||||||
|
|
||||||
|
export default function Register() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const { register } = useAuth();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [done, setDone] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function submit(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setBusy(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await register(email, password);
|
||||||
|
setDone(true);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof ApiError ? err.message : t("error.generic"));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 420 }}>
|
||||||
|
<h1 className="h3 mb-4">{t("register.title")}</h1>
|
||||||
|
{done ? (
|
||||||
|
<Alert variant="success">{t("register.checkEmail")}</Alert>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{error && <Alert variant="warning">{error}</Alert>}
|
||||||
|
<Form onSubmit={submit}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("register.email")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("register.password")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
minLength={8}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button type="submit" disabled={busy} className="w-100">
|
||||||
|
{t("register.submit")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
<p className="mt-3 small">
|
||||||
|
<Link to="/login">{t("register.haveAccount")}</Link>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
helexa.ai/src/pages/auth/RequestReset.tsx
Normal file
48
helexa.ai/src/pages/auth/RequestReset.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Alert, Button, Container, Form } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { accountApi } from "../../api/account";
|
||||||
|
|
||||||
|
export default function RequestReset() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [done, setDone] = useState(false);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function submit(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setBusy(true);
|
||||||
|
// Always succeeds from the UI's view (no account enumeration).
|
||||||
|
try {
|
||||||
|
await accountApi().requestReset(email);
|
||||||
|
} catch {
|
||||||
|
/* swallow */
|
||||||
|
}
|
||||||
|
setDone(true);
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 420 }}>
|
||||||
|
<h1 className="h3 mb-4">{t("reset.requestTitle")}</h1>
|
||||||
|
{done ? (
|
||||||
|
<Alert variant="info">{t("reset.requestDone")}</Alert>
|
||||||
|
) : (
|
||||||
|
<Form onSubmit={submit}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("reset.email")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button type="submit" disabled={busy} className="w-100">
|
||||||
|
{t("reset.requestSubmit")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
64
helexa.ai/src/pages/auth/ResetPassword.tsx
Normal file
64
helexa.ai/src/pages/auth/ResetPassword.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Link, useSearchParams } from "react-router-dom";
|
||||||
|
import { Alert, Button, Container, Form } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { accountApi } from "../../api/account";
|
||||||
|
import { ApiError } from "../../api/types";
|
||||||
|
|
||||||
|
export default function ResetPassword() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [done, setDone] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function submit(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = params.get("token");
|
||||||
|
if (!token) {
|
||||||
|
setError(t("verify.failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBusy(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await accountApi().confirmReset(token, password);
|
||||||
|
setDone(true);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof ApiError ? err.message : t("error.generic"));
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 420 }}>
|
||||||
|
<h1 className="h3 mb-4">{t("reset.confirmTitle")}</h1>
|
||||||
|
{done ? (
|
||||||
|
<Alert variant="success">
|
||||||
|
{t("reset.ok")} <Link to="/login">{t("verify.toLogin")}</Link>
|
||||||
|
</Alert>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{error && <Alert variant="warning">{error}</Alert>}
|
||||||
|
<Form onSubmit={submit}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>{t("reset.newPassword")}</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
minLength={8}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button type="submit" disabled={busy} className="w-100">
|
||||||
|
{t("reset.confirmSubmit")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
helexa.ai/src/pages/auth/VerifyEmail.tsx
Normal file
40
helexa.ai/src/pages/auth/VerifyEmail.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link, useSearchParams } from "react-router-dom";
|
||||||
|
import { Alert, Container, Spinner } from "react-bootstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { accountApi } from "../../api/account";
|
||||||
|
|
||||||
|
export default function VerifyEmail() {
|
||||||
|
const { t } = useTranslation("account");
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
const [state, setState] = useState<"verifying" | "ok" | "failed">("verifying");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = params.get("token");
|
||||||
|
// Keep all setState in async callbacks (no synchronous setState in the
|
||||||
|
// effect body): a missing token resolves to a rejected promise.
|
||||||
|
const run = token ? accountApi().verify(token) : Promise.reject(new Error("no token"));
|
||||||
|
run.then(() => setState("ok")).catch(() => setState("failed"));
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-5 flex-grow-1" style={{ maxWidth: 480 }}>
|
||||||
|
{state === "verifying" && (
|
||||||
|
<p>
|
||||||
|
<Spinner size="sm" className="me-2" />
|
||||||
|
{t("verify.verifying")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{state === "ok" && (
|
||||||
|
<Alert variant="success">
|
||||||
|
{t("verify.ok")} <Link to="/login">{t("verify.toLogin")}</Link>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{state === "failed" && (
|
||||||
|
<Alert variant="warning">
|
||||||
|
{t("verify.failed")} <Link to="/login">{t("verify.toLogin")}</Link>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
rpm/helexa-upstream-prerelease.spec
Normal file
109
rpm/helexa-upstream-prerelease.spec
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Prebuilt-binary spec for helexa-upstream.
|
||||||
|
#
|
||||||
|
# Wraps a pre-built `helexa-upstream` binary produced by an upstream CI
|
||||||
|
# job and packages it for rpm.lair.cafe. The %build phase is a no-op.
|
||||||
|
# helexa-upstream is a pure-Rust, non-CUDA daemon: the mesh account +
|
||||||
|
# budget authority. It serves an inbound HTTP API on tcp/8090 (/authz/v1
|
||||||
|
# for cortex, /web/v1 for the frontend) — hence the firewalld service —
|
||||||
|
# and connects out to PostgreSQL (the system of record), running schema
|
||||||
|
# migrations on startup.
|
||||||
|
#
|
||||||
|
# Required defines at rpmbuild time:
|
||||||
|
# upstream_version e.g. "0.1.16"
|
||||||
|
# upstream_prerelease e.g. "0.1.20260518140530.gitabcdef0"
|
||||||
|
# ^^^^^^^^^^^^^^^^^^ ^^^^^^^^
|
||||||
|
# commit time (sec) commit sha
|
||||||
|
# (used as Release; the timestamp prefix
|
||||||
|
# keeps same-day builds strictly ordered.)
|
||||||
|
|
||||||
|
%global _build_id_links none
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%global __strip /usr/bin/true
|
||||||
|
|
||||||
|
%{!?upstream_version: %global upstream_version 0.0.0}
|
||||||
|
%if 0%{?upstream_prerelease:1}
|
||||||
|
%global upstream_release %{upstream_prerelease}
|
||||||
|
%else
|
||||||
|
%global upstream_release 1
|
||||||
|
%endif
|
||||||
|
|
||||||
|
Name: helexa-upstream
|
||||||
|
Version: %{upstream_version}
|
||||||
|
Release: %{upstream_release}%{?dist}
|
||||||
|
Summary: Mesh account + budget authority for helexa (prebuilt)
|
||||||
|
|
||||||
|
License: GPL-3.0-or-later
|
||||||
|
URL: https://git.lair.cafe/helexa/helexa
|
||||||
|
|
||||||
|
Source0: helexa-upstream
|
||||||
|
Source1: helexa-upstream.service
|
||||||
|
Source2: helexa-upstream-sysusers.conf
|
||||||
|
Source3: helexa-upstream.example.toml
|
||||||
|
Source4: LICENSE
|
||||||
|
Source5: helexa-upstream-firewalld.xml
|
||||||
|
|
||||||
|
Requires: firewalld-filesystem
|
||||||
|
|
||||||
|
ExclusiveArch: x86_64
|
||||||
|
|
||||||
|
Requires(pre): shadow-utils
|
||||||
|
Requires: systemd
|
||||||
|
|
||||||
|
Provides: user(helexa-upstream)
|
||||||
|
|
||||||
|
%description
|
||||||
|
helexa-upstream is the mesh-level authority: it issues user accounts and
|
||||||
|
API keys, holds the allocation ledger (free grant + redeemable top-up
|
||||||
|
codes), enforces per-key budgets via a transactional reserve/settle
|
||||||
|
contract, and reconciles served usage for operator compensation. cortex
|
||||||
|
validates locally-unrecognised keys against its /authz/v1 surface
|
||||||
|
(fail-closed); the helexa.ai frontend drives account self-service via
|
||||||
|
/web/v1. PostgreSQL is the system of record; the schema is migrated on
|
||||||
|
startup.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
cp %{SOURCE0} ./helexa-upstream
|
||||||
|
cp %{SOURCE1} .
|
||||||
|
cp %{SOURCE2} .
|
||||||
|
cp %{SOURCE3} .
|
||||||
|
cp %{SOURCE4} .
|
||||||
|
cp %{SOURCE5} .
|
||||||
|
|
||||||
|
%build
|
||||||
|
# Already built in the upstream CI build job.
|
||||||
|
|
||||||
|
%install
|
||||||
|
install -Dm755 helexa-upstream %{buildroot}%{_bindir}/helexa-upstream
|
||||||
|
install -Dm644 helexa-upstream.service %{buildroot}%{_unitdir}/helexa-upstream.service
|
||||||
|
install -Dm644 helexa-upstream-sysusers.conf %{buildroot}%{_sysusersdir}/helexa-upstream.conf
|
||||||
|
install -Dm644 helexa-upstream-firewalld.xml %{buildroot}%{_prefix}/lib/firewalld/services/helexa-upstream.xml
|
||||||
|
install -dm755 %{buildroot}%{_sysconfdir}/helexa-upstream
|
||||||
|
install -Dm644 helexa-upstream.example.toml %{buildroot}%{_sysconfdir}/helexa-upstream/helexa-upstream.toml
|
||||||
|
|
||||||
|
%pre
|
||||||
|
getent group helexa-upstream >/dev/null || groupadd -r helexa-upstream
|
||||||
|
getent passwd helexa-upstream >/dev/null || \
|
||||||
|
useradd -r -g helexa-upstream -d /var/lib/helexa-upstream -s /sbin/nologin \
|
||||||
|
-c "helexa-upstream authority" helexa-upstream
|
||||||
|
|
||||||
|
%post
|
||||||
|
%systemd_post helexa-upstream.service
|
||||||
|
|
||||||
|
%preun
|
||||||
|
%systemd_preun helexa-upstream.service
|
||||||
|
|
||||||
|
%postun
|
||||||
|
%systemd_postun_with_restart helexa-upstream.service
|
||||||
|
|
||||||
|
%files
|
||||||
|
%license LICENSE
|
||||||
|
%{_bindir}/helexa-upstream
|
||||||
|
%{_unitdir}/helexa-upstream.service
|
||||||
|
%{_sysusersdir}/helexa-upstream.conf
|
||||||
|
%{_prefix}/lib/firewalld/services/helexa-upstream.xml
|
||||||
|
%dir %{_sysconfdir}/helexa-upstream
|
||||||
|
%config(noreplace) %{_sysconfdir}/helexa-upstream/helexa-upstream.toml
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Mon Jun 23 2026 Gitea Actions <actions@git.lair.cafe> - %{upstream_version}-%{upstream_release}
|
||||||
|
- Prerelease build from upstream CI binary.
|
||||||
Reference in New Issue
Block a user