Fixes#13751
## The crash
vue-i18n compiles messages just-in-time (`__INTLIFY_JIT_COMPILATION__`),
and a compile error is **thrown**, not warned — it tears down whatever
component asked for the message. The translation pipeline had been
localizing placeholder *names*, which is exactly that kind of error:
```
en-US "Are you sure you want to delete {identifier}?"
zh-CN "您确定要删除 {标识符} 吗?" -> SyntaxError: 2 (INVALID_TOKEN_IN_PLACEHOLDER)
```
IAM → Service Accounts rendered blank in Chinese for this reason. The
two zh-CN strings named in the issue had already been patched by hand in
#13707, but the *class* was never swept.
## What was actually broken
Compiling every message in every locale file with vue-i18n's own
compiler found three groups:
| | count | symptom |
|---|---|---|
| Does not compile | 132 | page blanks with `SyntaxError` — 8 locales:
es, fr, it, ja, ko, nl, pt, tr |
| Compiles, unknown name | 147 | `{Anzahl}` for `{count}` → value
renders as an empty string |
| Stale text, dropped placeholder | 5 | `{org}`, `{payer}`,
`{endpoint}`, `{service}` — English source no longer has them |
Worst of the first group are mangled vue-i18n literal escapes, e.g.
`{'{'}team{'}'}` coming back as `{'{'} '}` — an unbalanced brace.
All 279 are corrected here. The existing translated prose is kept
verbatim; only the tokens are restored. The 5 stale ones are
retranslated from the current English text, since restoring a token that
no longer exists is not an option.
## Why it can't come back
**`web/src/locales/enUsMessages.spec.ts` already did this — for en-US
only.** That left the machine-translated locales, the actual source of
the bug, uncovered. Renamed to `localeMessages.spec.ts` and extended to
every locale, plus a second assertion that a translation only
interpolates names en-US defines.
That second check is a **subset** rule, not equality: dropping a
placeholder the target language does not need stays legal (an English
plural suffix like `{plural}` has no Japanese equivalent). Introducing a
name en-US never passes a value for is always a bug.
Placeholder names are collected by *running* the compiled message
against a recording context rather than by pattern-matching the source,
because only the compiler knows which braces are placeholders and which
are literal escapes — `{'}'}` is one token whose body is the character a
`{...}` regex stops at. All plural branches are evaluated.
**`scripts/translations/translator.py` — `_validate` had the same regex
blind spot** (`{[^{}]*}`) and never checked that the output compiles at
all, which is why these landed. It now tokenizes the way vue-i18n does
and rejects anything vue-i18n would refuse, so the pipeline stops
emitting them rather than CI merely catching them afterwards. Escape
drift (`{'{'} rows {'} rows {'}`) is caught too, because literal escapes
are now part of the compared token multiset.
## Verification
- The Python scanner was cross-checked against vue-i18n's compiler over
**all 170,055 messages** in the 15 locale files — identical compile
verdicts and identical token sets, 0 divergences.
- Injecting the original `{标识符}` string back into zh-CN fails the new
spec; injecting a compiling-but-wrong `{bilv}` fails the second
assertion.
- `npx vitest run --environment jsdom --root src/ locales/` → 45 passed.
`prettier --check` and `tsc` clean on the spec.
- Only `en-US.json` is imported anywhere in `web/src`, so no existing
test depends on the changed locale data.
## Not in scope
A handful of translations are awkward but compile and bind correctly
(e.g. ko-KR renders "ingestion limit" with the eating sense of the
word). Left alone to keep this diff auditable as a mechanical token
restoration.
Supersedes #13738, whose commit is included here as the first of four.
Adds PromQL support to count and time-sliced SLOs, lets metrics streams
choose SQL or PromQL, and fixes three shipped bugs found along the way.
## Shipped bug: every SQL time-slice SLO recorded wrong history
`fetch_rows`' `SliQueryPlan::Single` arm normalized every hit with
`with_good_column: true`, but that arm serves **two** shapes and only
one projects `zo_slo_good`:
| shape | projects | result |
|---|---|---|
| count single-query | `zo_slo_good` + `zo_slo_value` | correct |
| time slice | `zo_slo_value` only | `good` defaulted to `0.0` |
So every SQL time-slice slice was classified from `0.0` instead of its
aggregate. With a positive threshold, `<`/`<=` (e.g. `p95 < 300ms`)
recorded every slice **good** — the SLO read ~100%, the budget never
burned, and no alert on it could fire. `>`/`>=` recorded every slice
**bad** and fired continuously. Shipped in v0.92.0 (`4772df1723`);
`to_row` had no time-slice test, which is how it got through.
Fixed by deriving the flag from the SLI type in a new
`single_query_rows`. Deliberately **not** fixed inside `to_row` by
falling back to `total` — that would read a count bucket whose `SUM`
came back NULL as *fully good*, inventing uptime. A test forbids it.
**Stored history is not rewritten.** It ages out within one window; an
operator can force a rebuild sooner by making a real edit (a no-op
re-save does nothing — `update` bumps only on `definition_changed`). No
auto-migration: bumping every time-slice SLO at boot would freeze every
attached alert org-wide until backfill cleared the coverage floor, and
double each org's row budget for 97 days — a self-inflicted monitoring
gap to repair a reporting error.
## PromQL support
**Time slice** carried a `query_language` field the planner ignored, so
a PromQL time-slice SLO saved cleanly and measured nothing, forever.
- `SliQueryPlan::PromQlValue` — one expression, not a good/total pair,
planned at slice ends from the same helper the count arm uses.
- The expression is passed through **verbatim**. Grouping comes from
series labels; wrapping it in `sum by (...)` the way the SQL arm injects
GROUP BY would destroy the aggregate (the sum of four pods' p95 is not a
p95).
- `promql_value_rows` **rejects** two series colliding on one
`(slice_start, group_key)` via `RejectReason::AmbiguousSeries` instead
of summing. Summing is right for counters (what `promql_rows` does) and
meaningless here. Detection is unconditional, so the ungrouped case — an
unaggregated `histogram_quantile` returning one series per instance — is
covered. Rejects are plumbed through `fetch_rows` into
`PassResult.rejected` on both the live and backfill paths.
**Count** already had a working `CountSource::PromQl` backend that no UI
could reach.
## Metrics streams choose SQL or PromQL
`language_suits_stream` refused SQL whenever `stream_type == "metrics"`,
and the form derived the language from the stream type — so metrics had
no SQL option. A metrics stream is an ordinary stream with a `value`
column, and `SELECT histogram(_timestamp, '5 minute'), avg(value) FROM
cpu_usage` is a perfectly good SLI. SQL is now valid for any stream
type; PromQL stays metrics-only. The form asks via a toggle (defaulting
to PromQL) instead of deriving.
## Validation
- `scope` refused for PromQL — a SQL `WHERE` fragment with nowhere to go
in a PromQL plan.
- Empty `query` refused for **both** languages (the SQL aggregate was
previously never validated — same silent-freeze shape).
- Expressions parsed with `promql-parser` at save, in `core` (`config`
has no parser).
- **Parse-only by design.** A grouping check requiring the root to be an
aggregation whose `by(...)` matches `group_by` wrongly rejects
`histogram_quantile(0.95, sum by (le, region)(...))` (function-call
root; `le` is a necessary label superset) and binary-op roots like `sum
by (region)(increase(a[5m])) - sum by (region)(increase(b[5m]))`. A
"warn instead" variant needs a warnings channel the save API doesn't
have.
## Also fixed
- **Time-slice comparator 422.** `SliConfig::TimeSlice.comparator` has
no serde default, so a dropdown the user never opened was omitted and
the save died with a bare 422 — blocking creation of *any* time-slice
SLO from the UI. Seeded to `<` only when blank, so a stored comparator
survives an edit.
- **SLO/alert/service-stream regressions** (the commit from #13738):
fractional-threshold serde round-trip, `SloAlertCondition` UI, alerts
table, service streams.
## Verification
- 487 config · 206 core · 45 infra · 418 frontend — all passing; clippy
`-D warnings`, `cargo fmt`, prettier, eslint, `vue-tsc` all clean.
- Driven through a real browser against a running server (isolated
instance, own data dir). All four SLI/language combinations create and
store correctly: count·PromQL, count·**SQL over metrics**,
time-slice·PromQL, time-slice·**SQL over metrics**. Also verified: the
toggle swaps field shapes both ways, scope shows for SQL and hides for
PromQL, PromQL-only hints appear only in PromQL mode, and editing a
stored SQL-over-metrics SLO reopens as SQL with its predicate intact.
- Validation rejections confirmed live with actionable messages:
PromQL-with-scope, unparseable PromQL, and an empty SQL aggregate.
Known limitation, pre-existing: `threshold` likewise has no serde
default and is not seeded, so leaving it blank still 422s. That is
genuine user error rather than an untouched-control trap; adding form
validation was out of scope.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The translation job ran on `push: main` and opened a separate
`chore/update-translations` PR. That PR had no owner — it was never
assigned to or reviewed by a human — and it left a window where main
carried English strings with no translations.
Move the trigger to `pull_request` and commit the generated locale files
back onto the PR's own head branch, so the author who added the English
strings also owns their translations and both land in one merge.
Notes on the mechanics:
- The commit is pushed with TRANSLATIONS_APPROVE_TOKEN, not
GITHUB_TOKEN. GitHub does not trigger workflows for GITHUB_TOKEN pushes,
so the new head SHA would carry no status checks and required checks
could never pass — the PR would be wedged. Falls back to GITHUB_TOKEN if
unset.
- A guard step skips the run when the head commit is already the
generated translation commit, so the PAT push cannot ping-pong.
- Fork PRs are skipped: their branch lives in the contributor's repo and
no token here can push to it.
- concurrency flips to cancel-in-progress: true. The `false` was
main-specific (merges landed faster than a run finished); on a PR branch
nothing is committed until a run completes and the surviving run
re-derives the pending set from .translation_state.json, so a cancel
costs spend, never progress.
- The push retries with a rebase in case the author pushes concurrently.
Cost does not regress: re-translation across branches is prevented by
the committed per-key hashes in .translation_state.json, not by the
trigger, so each string is still translated once — on whichever branch
first introduces it.
Restrict npm-check-updates to in-major upgrades (--target minor --peer).
Unrestricted major bumps pushed typescript from 5.9.x to 7.0.x, which
drops the ./lib/tsc export that vue-tsc still resolves, failing the type
check with ERR_PACKAGE_PATH_NOT_EXPORTED. Majors held back are now
listed in the PR body so they stay visible for a deliberate manual
upgrade.
Rename the branch to chore/npm-update-<date> and the PR title to
chore(deps): update web npm dependencies, matching the repo convention
used by update-translations.yml instead of the automated/ prefix.
Also fix along the way:
- npm ci before checking for updates, so reported current versions are
real
- gate has_updates on ncu output instead of npm outdated, so the job no
longer runs a full build cycle only to find nothing to commit
- write scratch files to RUNNER_TEMP so an untracked outdated.json
cannot make the commit step think dependencies changed
- scope the change check to web/package.json and web/package-lock.json
- split the commit message into separate -m flags to drop YAML
indentation
Design at: alerts_2.md
Per-group and per-series alerting, SLO measurement, and the UI for both.
## Alerts
**Multi-alerts (Feature 3).** A grouped alert can opt in to evaluating
and paging **per group** rather than collapsing to one verdict. Opt-in
only (`multi_alert`), so no existing alert changes behaviour: the flag
cannot be present in JSON written before it existed. Per-group state,
transitions, silence fingerprints, disappearance/reaping and a
cardinality cap, with the count-gate rules that make "most severe group"
and the legacy collapsed level provably the same verdict.
**Per-series alerting for PromQL.** The same feature for the metrics
family, where a group is one returned **series** keyed by its full label
set — that is a series' identity in Prometheus, and the expression's own
`by (…)` clause is already where the label set is chosen, so a second
picker could only disagree with it. One additive nullable column; NULL
means off, so stored alerts are untouched.
**Alert detail page**, replacing the row-click side panel: stat strip,
evaluation chart with threshold marklines, per-group table and history.
Simple alerts get a real history too — evaluations (one record per run)
by default, level changes alongside.
## SLOs
Count / time-slice / alert SLIs, error-budget and burn-rate arithmetic,
rolling windows, per-group slices with an exact rollup, coverage gating,
backfill, and SLO-based alerts.
Two capabilities the model could not express, both added tests-first:
- **Metrics-native counting** (`CountSource::PromQl`) — counter metrics
have no rows for a predicate to classify, so "good" exists only as
arithmetic between series, and correct counter arithmetic is
`increase()`.
- **Freshness semantics** (`absent_is_bad`) — for a pipeline SLO,
silence IS the failure, which the default gap rule can never report. A
*failed* query still freezes rather than pages; only a query that PROVED
emptiness counts as bad.
**SLO form**: live good/bad preview so a wrong predicate is caught
before saving rather than days later, real query editors with field
autocomplete, folders shared with alerts, and a move endpoint.
## Notes for review
- The SLO and composite features are **deferred (TODO)**:
`ZO_SLO_ENABLED` stays default **false** and the SLOs menu entry is
hidden until they ship; the SLO backend and UI remain in the tree behind
the flag, and composites stay pure logic + tests.
- One migration (schema 61): a nullable column for the PromQL opt-in. No
backfill.
- Two fixes outside the feature that it surfaced: query-editor
completions were duplicated per mounted editor (Monaco registers
providers per *language*), and the alert detail chart had no PromQL
branch, so it charted row counts against the series-count gate.
- Lands together with the matching o2-enterprise PR — `QueryType::Slo`
is new here and the enterprise dedup matches are exhaustive over it.
- `ratelimit::test_add_batch_missing_rule_id` fails intermittently on
`main` as well; unrelated to this branch.
---------
Co-authored-by: Shrinath Rao <shnath@openobserve.ai>
Co-authored-by: hengfeiyang <hengfei.yang@gmail.com>
Co-authored-by: sai nikhil kethe <nikhil@openobserve.ai>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en-US.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
chore: update package.json scripts for improved type-checking
fix: adjust frontmatter regex to handle BOM characters
refactor: streamline error handling in dashboard data conversion
style: clean up comments and formatting in various files
## Summary
Replaces the AWS Translate i18n pipeline with a DeepSeek (LLM) backend,
standardizes locale files to BCP-47 naming with an `en-US` base, adds
new languages, and backfills **every locale to 100%**.
## What changed
### Translation engine (DeepSeek)
- Swap AWS Translate → DeepSeek via its OpenAI-compatible API
(`scripts/translations/`). Auth via the `DEEPSEEK_API_KEY` secret;
per-request 60s timeout.
- **Batched** translation (50/call) with **per-string validation**:
interpolation placeholders (`{count}`, `%s`, `@:key`), vue-i18n
**pluralization pipes** (`{n} Day \| {n} Days`), and literal escapes
(`{'@'}`, `{'{'}`, `{'\|'}`) are preserved or the string is rejected and
retried.
- Robust recovery: index-aligned batches, split-on-mismatch to isolate a
single bad string, temperature-perturbed retries, and rejection of
empty/whitespace output (never blanks a label). Removed the old
`TRANSLATION_MAX_KEYS` safety cap.
### Locale naming + base language
- All 15 locale files renamed to **BCP-47 hyphen** form (`en-US.json`,
`zh-CN.json`, `zh-TW.json`, `fr-FR.json`, …) — matching
`navigator.language` / `Intl` / HTML `lang`.
- **English base switched `en-gb` → `en-us`** (app code,
`fallbackLocale`, BCP-47 map, selector, defaults). British-Pound
currency and date-formatting `en-GB` usages left untouched. Existing
`en-gb` cookies fall back cleanly to English — no migration needed.
### New languages
- **Russian (`ru`), Polish (`pl`), Vietnamese (`vi`)** — fully generated
and wired into `localeFileMap`, `APP_LOCALE_TO_BCP47`, and the language
selector.
- **Traditional Chinese (`zh-TW`)** now maintained by the translator
alongside Simplified (`zh-CN`).
- RTL languages (`ar`, `fa`) intentionally **deferred** until the app
has `dir="rtl"` support.
### Full backfill (one-time)
- Every pre-existing locale filled from ~5.9k up to the full **8,269
keys** (~26.9k strings). `.translation_state.json` rebuilt complete, so
future runs are incremental.
### Nav i18n bug fix
- The **Dashboards** and **Data** nav group labels were hardcoded
English (bypassing i18n) and stayed English in every locale. Now
resolved via `t()` (`menu.dashboard`, new `menu.data`), localized and
re-resolving on language change.
## Verification
- Full web unit suite: **39,035 passed / 414 skipped / 0 failed** (1,167
files).
- `type-check` clean (0 TS errors).
- Locale registry sync spec green; all 15 files = 8,269 leaves.
- Translation engine verified live against `deepseek-v4-flash`.
## Notes
- Since all locales are now 100%, the post-merge `update-translations`
workflow will find nothing pending (no follow-up translation PR).
- `ar`/`fa` to be added with RTL support in a follow-up.
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: oasisk <35160958+oasisk@users.noreply.github.com>
Co-authored-by: Hengfei Yang <hengfei.yang@gmail.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Auto-generated translation updates from the English source file
(`web/src/locales/languages/en.json`).
🤖 Generated with automated translation workflow
Co-authored-by: github-merge-queue[bot] <118344674+github-merge-queue[bot]@users.noreply.github.com>
Follow-up tooling to
[#12946](https://github.com/openobserve/openobserve/pull/12946). Pairs
with the [ENT MCP-fixes
PR](https://github.com/openobserve/o2-enterprise/pull/2072) to complete
the loop from "engine works on one issue" → "batch of 100 fires in
parallel and lands verdicts in a reviewable table."
## Summary
- **`scripts/test-assist-ci-batch.sh`** — POSIX bash script that
dispatches the test-assist engine (in o2-enterprise) for a list of OSS
issues. Reads issue numbers from stdin or positional args. Supports
`--dry-run`, `--write-mode`, `--throttle`. Uses `gh workflow run` under
the maintainer's local `gh auth`. Each dispatch is fire-and-forget; the
engine's own concurrency group (`source_repo+issue`) enforces
one-run-per-issue-at-a-time.
- **`qa-reports/test-assist-ci-backlog-sweep.md`** — Roll-up table where
verdicts land after each batch. Documents the tier structure of the
priority-100 list, the verdict-to-action map (FIXED / STILL_PRESENT /
INCONCLUSIVE / SKIP), and the tier queries used to build the list. Table
starts empty; batches append rows.
## Usage
```bash
# One-shot: dispatch for a single issue
./scripts/test-assist-ci-batch.sh 12452
# Batch: dispatch for many issues in parallel (from stdin)
gh issue list --repo openobserve/openobserve --label "☢️ Bug" --label "Retested-Failed" --state open --limit 30 \
--json number --jq '.[].number' \
| ./scripts/test-assist-ci-batch.sh
# Dry-run to see what would fire (no dispatches)
./scripts/test-assist-ci-batch.sh --dry-run 12452 11700 12647
# Explicitly enable label+comment writes (only after soak calibration)
./scripts/test-assist-ci-batch.sh --write-mode < /tmp/batch.txt
```
## Interaction with the `Test-Assist-CI-Verified` label
The ENT engine ([PR
#2072](https://github.com/openobserve/o2-enterprise/pull/2072)) applies
the `Test-Assist-CI-Verified` label to each processed issue as factual
metadata (independent of `write_mode`). After a batch:
- [Filter checked
issues](https://github.com/openobserve/openobserve/issues?q=is%3Aissue+label%3A%22Test-Assist-CI-Verified%22)
→ the ones we've processed
- [Filter unchecked
issues](https://github.com/openobserve/openobserve/issues?q=is%3Aissue+is%3Aopen+label%3A%22%E2%98%A2%EF%B8%8F+Bug%22+-label%3A%22Test-Assist-CI-Verified%22)
→ the untouched backlog
## Test plan
- [ ] `bash -n scripts/test-assist-ci-batch.sh` — script parses
(verified locally)
- [ ] `./scripts/test-assist-ci-batch.sh --dry-run 12452` — prints "DRY:
would dispatch #12452"
- [ ] After ENT PR #2072 merges: fire a single-issue dispatch via the
script → confirm run appears in o2-enterprise Actions
- [ ] Fire the 100-issue batch → confirm verdicts populate the sweep
markdown
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
added workflow to udpate the language conversion
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>