feat(billing): add per-resolution image model billing#5035
Conversation
Signed-off-by: Micah-Zheng <102610064+Micah-Zheng@users.noreply.github.com>
- model-pricing-sheet: reset perRequestSubMode/price1k/price2k/price4k in else branch when switching to add-new-model mode - model-pricing-sheet: wrap Sub-mode/1K price/2K price/4K price and resolution tier labels with t() for i18n consistency - model-ratio-visual-editor: add imageModelSetting to React.memo comparator so prop updates are not silently ignored - model-ratio-visual-editor: hoist safeJsonParse(imageModelSetting) out of per-model .map() to avoid redundant JSON parsing - ratio-settings-card: validate ImageModelSetting as JSON in zod schema - ratio-settings-card: normalize ImageModelSetting in modelNormalizedDefaults and saveModelRatios to prevent false-positive dirty diffs - image_model_setting.go: add overflow guard to parsePositiveInt - zh.json: fix auth.resetPasswordConfirm.retry unit from 's' to '秒' - i18n: add Sub-mode/1K price/2K price/4K price/tier label keys to all locales Signed-off-by: Micah-Zheng <102610064+Micah-Zheng@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThis PR implements per-resolution image billing for image-generation models: adds image-model config and index rebuild, runtime size-tier classification and per-size surcharge in quota calculation, admin UI for per-resolution tier pricing, usage-log display, and i18n updates. ChangesPer-Size Image Billing Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Signed-off-by: Micah-Zheng <102610064+Micah-Zheng@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx`:
- Around line 296-303: The image billing row uses image_per_size_count and
image_size_tier directly which lets 0 and empty string appear; before pushing
the row (in the block guarded by other.image_per_size_billing &&
other.image_per_size_price != null) clamp the count to a positive integer (e.g.,
const count = Math.max(1, other.image_per_size_count ?? 1)) and default the tier
to a concrete fallback like '2K' (e.g., const tier = other.image_size_tier ??
'2K'), then use those sanitized values in the rows.push call that constructs the
label/value (rows.push with label 'Image Generation' and value using fmtPrice
and t).
In `@web/default/src/i18n/locales/_reports/_sync-report.json`:
- Around line 12-38: _sync-report.json shows missingCount: 7 for all non-en
locales but actual key comparison between en.json and
fr.json/ja.json/ru.json/vi.json/zh.json shows no missing leaf keys; regenerate
the i18n sync report (or re-run the sync tool) so missingCount reflects current
key coverage, or update the report generation logic to compute missingCount the
same way the key-comparison uses (handle normalization/empty values/leaf-key
resolution consistently); specifically re-run the reporter that produces
_reports/_sync-report.json and verify the fields "missingCount",
"untranslatedCount", and listed untranslated keys match the outputs from compare
of en.json against fr.json/ja.json/ru.json/vi.json/zh.json, then commit the
regenerated _sync-report.json and adjust PR text if the metric semantics
changed.
In `@web/default/src/i18n/locales/_reports/ja.untranslated.json`:
- Around line 87-90: Replace the flat English translation keys ("Sub-mode", "1K
price", "2K price", "4K price") with hierarchical, semantically clear keys to
match project conventions; for example change those keys to modelPricing.subMode
(or modelPricing.requestMode.subMode), modelPricing.tier1KPrice,
modelPricing.tier2KPrice and modelPricing.tier4KPrice in ja.untranslated.json
and apply the same renames in ru.untranslated.json and zh.untranslated.json,
updating any code references that consume these keys to use the new dot-notated
keys so lookups remain correct.
In `@web/default/src/i18n/locales/fr.json`:
- Around line 2901-2904: The French locale file contains untranslated keys
"Sub-mode", "1K price", "2K price", and "4K price" which should be translated to
French; update the JSON entries for these keys in
web/default/src/i18n/locales/fr.json (the string keys "Sub-mode", "1K price",
"2K price", "4K price") with their proper French equivalents (e.g., translate to
"Sous-mode" and appropriate French phrases for price labels such as "Prix pour
1K", "Prix pour 2K", "Prix pour 4K" or your project's established wording)
ensuring you keep the same keys and valid JSON formatting.
In `@web/default/src/i18n/locales/ja.json`:
- Around line 2901-2907: The listed localization entries ("Sub-mode", "1K
price", "2K price", "4K price", "1K (≤ 1024px)", "2K (≤ 2048px)", "4K (>
2048px)") are still English in the ja.json locale; update each value to the
appropriate Japanese translation (e.g., translate "Sub-mode" and the size/price
labels and their parentheses/units) so the keys remain the same but their values
are Japanese text, preserving punctuation and units like "≤" and ">" exactly as
in the keys.
In `@web/default/src/i18n/locales/ru.json`:
- Around line 2901-2904: The Russian locale file ru.json contains untranslated
labels for billing sub-mode keys ("Sub-mode", "1K price", "2K price", "4K
price"); update their values to proper Russian translations (e.g., "Режим",
"Цена за 1К", "Цена за 2К", "Цена за 4К" or other approved wording) so RU users
see consistent Russian UI, leaving the keys unchanged and only modifying the
right-hand side strings in ru.json.
In `@web/default/src/i18n/locales/vi.json`:
- Around line 2901-2904: The four English labels "Sub-mode", "1K price", "2K
price", and "4K price" in the vi.json locale should be replaced with their
Vietnamese translations so the per-resolution pricing form is fully localized;
update the values for the keys "Sub-mode" (e.g., "Chế độ phụ"), "1K price"
(e.g., "Giá 1K"), "2K price" ("Giá 2K") and "4K price" ("Giá 4K") in the vi.json
file to the Vietnamese strings.
In `@web/default/src/i18n/locales/zh.json`:
- Around line 2901-2907: The new zh.json entries ("Sub-mode", "1K price", "2K
price", "4K price", "1K (≤ 1024px)", "2K (≤ 2048px)", "4K (> 2048px)") are still
in English; replace each value with an appropriate Chinese translation (e.g.,
translate "Sub-mode" to "子模式", "1K price" to "1K 价格", "2K price" to "2K 价格", "4K
price" to "4K 价格", and localize the resolution labels like "1K (≤ 1024px)" ->
"1K(≤1024px)", etc.) so the zh locale has no English fallbacks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8edcf33e-b26f-49a0-b445-499156ed20fb
📒 Files selected for processing (24)
model/option.gorelay/image_handler.goservice/text_quota.gosetting/operation_setting/image_model_setting.goweb/default/src/features/system-settings/billing/index.tsxweb/default/src/features/system-settings/billing/section-registry.tsxweb/default/src/features/system-settings/models/model-pricing-sheet.tsxweb/default/src/features/system-settings/models/model-ratio-form.tsxweb/default/src/features/system-settings/models/model-ratio-visual-editor.tsxweb/default/src/features/system-settings/models/ratio-settings-card.tsxweb/default/src/features/system-settings/types.tsweb/default/src/features/usage-logs/components/columns/common-logs-columns.tsxweb/default/src/features/usage-logs/components/dialogs/details-dialog.tsxweb/default/src/features/usage-logs/types.tsweb/default/src/i18n/locales/_reports/_sync-report.jsonweb/default/src/i18n/locales/_reports/ja.untranslated.jsonweb/default/src/i18n/locales/_reports/ru.untranslated.jsonweb/default/src/i18n/locales/_reports/zh.untranslated.jsonweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.json
| "missingCount": 7, | ||
| "extrasCount": 0, | ||
| "untranslatedCount": 21 | ||
| }, | ||
| "ja": { | ||
| "file": "ja.json", | ||
| "missingCount": 0, | ||
| "missingCount": 7, | ||
| "extrasCount": 0, | ||
| "untranslatedCount": 120 | ||
| "untranslatedCount": 124 | ||
| }, | ||
| "ru": { | ||
| "file": "ru.json", | ||
| "missingCount": 0, | ||
| "missingCount": 7, | ||
| "extrasCount": 0, | ||
| "untranslatedCount": 135 | ||
| "untranslatedCount": 139 | ||
| }, | ||
| "vi": { | ||
| "file": "vi.json", | ||
| "missingCount": 0, | ||
| "missingCount": 7, | ||
| "extrasCount": 0, | ||
| "untranslatedCount": 23 | ||
| }, | ||
| "zh": { | ||
| "file": "zh.json", | ||
| "missingCount": 0, | ||
| "missingCount": 7, | ||
| "extrasCount": 0, | ||
| "untranslatedCount": 99 | ||
| "untranslatedCount": 103 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Find all new translation keys in en.json that are missing from other locales
# Get the list of keys added to en.json (assuming this is the base locale)
echo "=== Checking for new keys in en.json ==="
rg -n '"(Sub-mode|1K price|2K price|4K price)":' web/default/src/i18n/locales/en.json
echo -e "\n=== Searching for any other per-resolution billing related keys ==="
# Look for related keys that might be the missing 3
rg -n '".*([Pp]er.?(size|resolution)|image.*price|tier.*price).*":' web/default/src/i18n/locales/en.json
echo -e "\n=== Checking coverage in all locale files ==="
for locale in fr ja ru vi zh; do
echo "--- $locale.json ---"
rg -c '"(Sub-mode|1K price|2K price|4K price)":' "web/default/src/i18n/locales/$locale.json" || echo "0 matches"
doneRepository: QuantumNous/new-api
Length of output: 1007
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List reports dir =="
ls -la web/default/src/i18n/locales/_reports || true
echo
echo "== Show sync report file header (first ~120 lines) =="
wc -l web/default/src/i18n/locales/_reports/_sync-report.json
sed -n '1,160p' web/default/src/i18n/locales/_reports/_sync-report.json
echo
echo "== Extract locale sections from sync report (missing/untranslated keys if present) =="
# Try to extract the ja/ru/zh/fr/vi blocks around counts
rg -n '"(fr|ja|ru|vi|zh)"\s*:\s*\{' -n web/default/src/i18n/locales/_reports/_sync-report.json
echo
python3 - <<'PY'
import json
p="web/default/src/i18n/locales/_reports/_sync-report.json"
with open(p,"r",encoding="utf-8") as f:
data=json.load(f)
# Print available top-level structure
print("Top-level keys:", list(data.keys())[:30])
for loc in ["fr","ja","ru","vi","zh"]:
if loc in data:
print("\n== ",loc,"==")
d=data[loc]
# show keys present in that locale object
print("fields:", list(d.keys()))
for k in ["file","missingCount","untranslatedCount","missingKeys","untranslatedKeys"]:
if k in d:
v=d[k]
if isinstance(v,list):
print(k, "len=",len(v))
print("sample:", v[:20])
else:
print(k, v)
PY
echo "== Show untranslated report files if present =="
for f in web/default/src/i18n/locales/_reports/*untranslated*.json; do
[ -f "$f" ] || continue
echo
echo "---- $f ----"
wc -l "$f"
# print first 120 lines (or whole if shorter)
if [ "$(wc -l <"$f")" -le 160 ]; then
cat -n "$f"
else
sed -n '1,160p' "$f"
fi
done
echo
echo "== If untranslated files include key arrays, list the keys for ja/ru/zh =="
python3 - <<'PY'
import glob, json
targets=["ja","ru","zh"]
paths=[]
for loc in targets:
# find best matching file
cand=glob.glob(f"web/default/src/i18n/locales/_reports/{loc}*untranslated*.json")
paths.append((loc,cand[0] if cand else None))
print(paths)
for loc,p in paths:
if not p:
continue
with open(p,"r",encoding="utf-8") as f:
data=json.load(f)
# Try common shapes
keys=[]
if isinstance(data,dict):
# maybe { "keys":[...] } or direct mapping
if "keys" in data and isinstance(data["keys"],list):
keys=data["keys"]
else:
# direct mapping: { "Some.key": "en string", ... }
keys=list(data.keys())
elif isinstance(data,list):
keys=data
print("\n==",loc,"== file:",p)
print("key count:",len(keys))
# show keys
for k in keys[:200]:
print(k)
PY
echo
echo "== Compare which keys exist in en.json but are absent in each locale json =="
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales/en.json")
en=json.loads(base.read_text(encoding="utf-8"))
locales=["fr","ja","ru","vi","zh"]
# flatten dot-keys for top-level keys only (reports likely use flat JSON keys)
# We'll restrict to keys that look like the newly mentioned ones:
candidates={"Sub-mode","1K price","2K price","4K price"}
# Determine missing keys overall by exact top-level keys.
en_keys=set(en.keys())
print("Total en top-level keys:",len(en_keys))
for loc in locales:
loc_path=Path(f"web/default/src/i18n/locales/{loc}.json")
d=json.loads(loc_path.read_text(encoding="utf-8"))
missing=sorted(en_keys - set(d.keys()))
print(f"\n== {loc} ==")
print("missing top-level keys count:",len(missing))
# show any that match candidates or include billing strings
match=[k for k in missing if any(c in k for c in candidates) or "K price" in k or "Sub-mode" in k or "price" in k or "resolution" in k]
print("missing keys matching billing-ish candidates:",len(match))
for k in match:
print("-",k)
PYRepository: QuantumNous/new-api
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales")
en=json.loads((base/"en.json").read_text(encoding="utf-8"))
en_keys=set(en.keys())
locales=["fr","ja","ru","vi","zh"]
for loc in locales:
loc_data=json.loads((base/f"{loc}.json").read_text(encoding="utf-8"))
loc_keys=set(loc_data.keys())
missing=sorted(en_keys-loc_keys)
print(f"\n== Missing in {loc}.json: {len(missing)} ==")
for k in missing:
print("-",k)
# Load untranslated report keys for this locale (if present)
u_path=base/"_reports"/f"{loc}.untranslated.json"
if u_path.exists():
untranslated=json.loads(u_path.read_text(encoding="utf-8"))
u_keys=set(untranslated.keys())
inter=[k for k in missing if k in u_keys]
print(f"Intersection(missing, untranslated report keys) for {loc}: {len(inter)}")
if inter:
for k in inter:
print(" -",k)
# Show which missing keys are in the 4 mentioned in review
watch=["Sub-mode","1K price","2K price","4K price"]
watch_present=[k for k in missing if k in watch]
print(f"Missing among watch keys {watch}: {len(watch_present)}/{len(watch_present)} -> {watch_present}")
PYRepository: QuantumNous/new-api
Length of output: 923
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales")
def load(p):
return json.loads(p.read_text(encoding="utf-8"))
en=load(base/"en.json")
print("Top-level en keys:", len(en))
# show top-level value types
types={}
for k,v in list(en.items())[:50]:
types[type(v).__name__]=types.get(type(v).__name__,0)+1
print("Sample top-level value types (first 50 keys):", types)
def find_paths(obj, target, path=()):
paths=[]
if isinstance(obj, dict):
for k,v in obj.items():
if k==target:
paths.append(path+(k,))
paths += find_paths(v, target, path+(k,))
elif isinstance(obj, list):
for i,v in enumerate(obj):
paths += find_paths(v, target, path+(str(i),))
return paths
for key in ["Sub-mode","1K price","2K price","4K price"]:
paths=find_paths(en,key)
print(f"\nPaths to key '{key}' in en.json (up to 10):", paths[:10])
ja=load(base/"ja.json")
print("\nTop-level ja keys:", len(ja))
for key in ["Sub-mode","1K price","2K price","4K price"]:
paths=find_paths(ja,key)
print(f"Paths to key '{key}' in ja.json (up to 10):", paths[:10])
PY
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales")
def load(p):
return json.loads(p.read_text(encoding="utf-8"))
def flatten_keys(obj, prefix=()):
# Returns set of "paths" to leaf values where leaf is non-dict/list
out=set()
if isinstance(obj, dict):
for k,v in obj.items():
out |= flatten_keys(v, prefix+(str(k),))
elif isinstance(obj, list):
for i,v in enumerate(obj):
out |= flatten_keys(v, prefix+(str(i),))
else:
out.add(".".join(prefix))
return out
en=load(base/"en.json")
for loc in ["fr","ja","ru","vi","zh"]:
d=load(base/f"{loc}.json")
en_keys=flatten_keys(en)
loc_keys=flatten_keys(d)
missing=en_keys-loc_keys
print(f"\n== Leaf-path missing in {loc}.json: {len(missing)} ==")
# show a few missing paths that look relevant
cand=[m for m in missing if any(s in m for s in ["Sub-mode","1K price","2K price","4K price","price"])]
print("Candidate missing (price/Sub-mode):", len(cand))
for m in sorted(cand)[:30]:
print("-",m)
# show first 20 missing paths overall
for m in sorted(missing)[:20]:
print(" -",m)
PY
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales")
def load(p): return json.loads(p.read_text(encoding="utf-8"))
en=load(base/"en.json")
loc=load(base/"fr.json")
# If missingCount could be "empty" values: check leaf paths where en has non-empty but locale has "" or None
def iter_leaf(obj, prefix=()):
if isinstance(obj, dict):
for k,v in obj.items():
yield from iter_leaf(v,prefix+(str(k),))
elif isinstance(obj, list):
for i,v in enumerate(obj):
yield from iter_leaf(v,prefix+(str(i),))
else:
yield (".".join(prefix), obj)
en_leaf=dict(iter_leaf(en))
loc_leaf=dict(iter_leaf(loc))
empties=[k for k,v in en_leaf.items() if v not in ("",None) and (k not in loc_leaf or loc_leaf[k] in ("",None))]
print("\nLeaf paths where en has non-empty but locale has empty/missing:", len(empties))
for k in sorted(empties)[:40]:
print("-",k)
PYRepository: QuantumNous/new-api
Length of output: 1236
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
base=Path("web/default/src/i18n/locales")
def load(loc):
return json.loads((base/f"{loc}.json").read_text(encoding="utf-8"))
def all_path_keys(obj, prefix=()):
# collect every dict key path (not just leaves)
out=set()
if isinstance(obj, dict):
for k,v in obj.items():
out.add(".".join(prefix+(str(k),)))
out |= all_path_keys(v, prefix+(str(k),))
elif isinstance(obj, list):
for i,v in enumerate(obj):
out |= all_path_keys(v, prefix+(str(i),))
return out
def leaf_paths(obj, prefix=()):
out=set()
if isinstance(obj, dict):
for k,v in obj.items():
out |= leaf_paths(v, prefix+(str(k),))
elif isinstance(obj, list):
for i,v in enumerate(obj):
out |= leaf_paths(v, prefix+(str(i),))
else:
out.add(".".join(prefix))
return out
en=load("en")
en_leaves=leaf_paths(en)
en_keys=all_path_keys(en)
print("en leaf count:",len(en_leaves))
print("en all-dict-key-path count:",len(en_keys))
watch=["translation.Sub-mode","translation.1K price","translation.2K price","translation.4K price","Sub-mode","1K price","2K price","4K price"]
for loc in ["fr","ja","ru","vi","zh"]:
d=load(loc)
leaves=leaf_paths(d)
keys=all_path_keys(d)
missing_leaves=en_leaves-leaves
missing_keys=en_keys-keys
print(f"\n== {loc} ==")
print("missing leaf paths:",len(missing_leaves))
print("missing dict-key-paths:",len(missing_keys))
# show if any watch paths are missing in either representation
for w in watch:
if w in missing_leaves or w in missing_keys:
print("MISSING watch path:",w)
PYRepository: QuantumNous/new-api
Length of output: 406
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -la web/default/src/i18n/locales/_reports
echo
echo "== Any *missing* report files? =="
ls -1 web/default/src/i18n/locales/_reports | rg -i 'missing' || true
echo
# Grep sync tool outputs for "missing" keys arrays if any
rg -n '"missingCount"|missingKeys|missing.*untranslated|missing' web/default/src/i18n/locales/_reports/_sync-report.json || true
echo
# Search for reports that might include missing keys for locales
for loc in fr ja ru vi zh; do
echo "== Potential missing report for $loc =="
ls -1 web/default/src/i18n/locales/_reports | rg -i "^${loc}.*missing.*\.json$" || true
done
# Also inspect locale untranslated reports for any keys that may be part of 'missing' (heuristic)
for f in web/default/src/i18n/locales/_reports/*untranslated*.json; do
echo
echo "---- $f ----"
# show if report contains keys that are not in locale.json (shouldn't happen), or just count lines
wc -l "$f"
doneRepository: QuantumNous/new-api
Length of output: 1657
Clarify i18n sync report missingCount=7 vs actual locale key coverage
The “3 unaccounted missing keys” claim doesn’t hold: comparing web/default/src/i18n/locales/en.json with fr.json/ja.json/ru.json/vi.json/zh.json shows no missing leaf keys, and the untranslated reports for ja/ru/zh include the 4 keys (“Sub-mode”, “1K price”, “2K price”, “4K price”) that were called out.
However, _reports/_sync-report.json still reports missingCount: 7 for all non-English locales. Either the report’s “missing” metric is computed differently than key presence (e.g., normalization/empties/other rules), or the report is stale—update/regenerate the sync report and align the PR description to what missingCount actually represents.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/_reports/_sync-report.json` around lines 12 -
38, _sync-report.json shows missingCount: 7 for all non-en locales but actual
key comparison between en.json and fr.json/ja.json/ru.json/vi.json/zh.json shows
no missing leaf keys; regenerate the i18n sync report (or re-run the sync tool)
so missingCount reflects current key coverage, or update the report generation
logic to compute missingCount the same way the key-comparison uses (handle
normalization/empty values/leaf-key resolution consistently); specifically
re-run the reporter that produces _reports/_sync-report.json and verify the
fields "missingCount", "untranslatedCount", and listed untranslated keys match
the outputs from compare of en.json against
fr.json/ja.json/ru.json/vi.json/zh.json, then commit the regenerated
_sync-report.json and adjust PR text if the metric semantics changed.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use hierarchical translation key names.
The new translation keys use plain English labels ("Sub-mode", "1K price", "2K price", "4K price") instead of hierarchical dot notation. This pattern also appears in ru.untranslated.json and zh.untranslated.json.
As per coding guidelines, translation keys should use hierarchical and semantically clear names such as dashboard.overview.title. For per-resolution pricing, consider keys like:
modelPricing.subModeormodelPricing.requestMode.subModemodelPricing.tier1KPricemodelPricing.tier2KPricemodelPricing.tier4KPrice
This improves semantic clarity, organization, and reduces potential key collisions.
As per coding guidelines: "Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/_reports/ja.untranslated.json` around lines 87 -
90, Replace the flat English translation keys ("Sub-mode", "1K price", "2K
price", "4K price") with hierarchical, semantically clear keys to match project
conventions; for example change those keys to modelPricing.subMode (or
modelPricing.requestMode.subMode), modelPricing.tier1KPrice,
modelPricing.tier2KPrice and modelPricing.tier4KPrice in ja.untranslated.json
and apply the same renames in ru.untranslated.json and zh.untranslated.json,
updating any code references that consume these keys to use the new dot-notated
keys so lookups remain correct.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", |
There was a problem hiding this comment.
Translate remaining English labels in the French locale.
Line 2901–2904 are still English (Sub-mode, 1K price, 2K price, 4K price), so FR users will see mixed-language UI in the new billing flow.
💡 Suggested fix
- "Sub-mode": "Sub-mode",
- "1K price": "1K price",
- "2K price": "2K price",
- "4K price": "4K price",
+ "Sub-mode": "Sous-mode",
+ "1K price": "Prix 1K",
+ "2K price": "Prix 2K",
+ "4K price": "Prix 4K",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/fr.json` around lines 2901 - 2904, The French
locale file contains untranslated keys "Sub-mode", "1K price", "2K price", and
"4K price" which should be translated to French; update the JSON entries for
these keys in web/default/src/i18n/locales/fr.json (the string keys "Sub-mode",
"1K price", "2K price", "4K price") with their proper French equivalents (e.g.,
translate to "Sous-mode" and appropriate French phrases for price labels such as
"Prix pour 1K", "Prix pour 2K", "Prix pour 4K" or your project's established
wording) ensuring you keep the same keys and valid JSON formatting.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", | ||
| "1K (≤ 1024px)": "1K (≤ 1024px)", | ||
| "2K (≤ 2048px)": "2K (≤ 2048px)", | ||
| "4K (> 2048px)": "4K (> 2048px)", |
There was a problem hiding this comment.
Untranslated strings in Japanese locale
These values are still English in ja.json, so Japanese users will see mixed-language UI.
Proposed translation patch
- "Sub-mode": "Sub-mode",
- "1K price": "1K price",
- "2K price": "2K price",
- "4K price": "4K price",
- "1K (≤ 1024px)": "1K (≤ 1024px)",
- "2K (≤ 2048px)": "2K (≤ 2048px)",
- "4K (> 2048px)": "4K (> 2048px)",
+ "Sub-mode": "サブモード",
+ "1K price": "1K 価格",
+ "2K price": "2K 価格",
+ "4K price": "4K 価格",
+ "1K (≤ 1024px)": "1K(≤ 1024px)",
+ "2K (≤ 2048px)": "2K(≤ 2048px)",
+ "4K (> 2048px)": "4K(> 2048px)",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/ja.json` around lines 2901 - 2907, The listed
localization entries ("Sub-mode", "1K price", "2K price", "4K price", "1K (≤
1024px)", "2K (≤ 2048px)", "4K (> 2048px)") are still English in the ja.json
locale; update each value to the appropriate Japanese translation (e.g.,
translate "Sub-mode" and the size/price labels and their parentheses/units) so
the keys remain the same but their values are Japanese text, preserving
punctuation and units like "≤" and ">" exactly as in the keys.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", |
There was a problem hiding this comment.
Untranslated Russian locale values in billing sub-mode labels
Line 2901-2904 are still English, so RU users will see mixed-language UI.
💡 Suggested fix
- "Sub-mode": "Sub-mode",
- "1K price": "1K price",
- "2K price": "2K price",
- "4K price": "4K price",
+ "Sub-mode": "Подрежим",
+ "1K price": "Цена 1K",
+ "2K price": "Цена 2K",
+ "4K price": "Цена 4K",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "Sub-mode": "Sub-mode", | |
| "1K price": "1K price", | |
| "2K price": "2K price", | |
| "4K price": "4K price", | |
| "Sub-mode": "Подрежим", | |
| "1K price": "Цена 1K", | |
| "2K price": "Цена 2K", | |
| "4K price": "Цена 4K", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/ru.json` around lines 2901 - 2904, The Russian
locale file ru.json contains untranslated labels for billing sub-mode keys
("Sub-mode", "1K price", "2K price", "4K price"); update their values to proper
Russian translations (e.g., "Режим", "Цена за 1К", "Цена за 2К", "Цена за 4К" or
other approved wording) so RU users see consistent Russian UI, leaving the keys
unchanged and only modifying the right-hand side strings in ru.json.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", |
There was a problem hiding this comment.
Translate newly added billing labels in Vietnamese locale
Line 2901–Line 2904 are still English in vi.json, so users will see mixed-language UI in the per-resolution pricing form.
💬 Suggested fix
- "Sub-mode": "Sub-mode",
- "1K price": "1K price",
- "2K price": "2K price",
- "4K price": "4K price",
+ "Sub-mode": "Chế độ phụ",
+ "1K price": "Giá 1K",
+ "2K price": "Giá 2K",
+ "4K price": "Giá 4K",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "Sub-mode": "Sub-mode", | |
| "1K price": "1K price", | |
| "2K price": "2K price", | |
| "4K price": "4K price", | |
| "Sub-mode": "Chế độ phụ", | |
| "1K price": "Giá 1K", | |
| "2K price": "Giá 2K", | |
| "4K price": "Giá 4K", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/vi.json` around lines 2901 - 2904, The four
English labels "Sub-mode", "1K price", "2K price", and "4K price" in the vi.json
locale should be replaced with their Vietnamese translations so the
per-resolution pricing form is fully localized; update the values for the keys
"Sub-mode" (e.g., "Chế độ phụ"), "1K price" (e.g., "Giá 1K"), "2K price" ("Giá
2K") and "4K price" ("Giá 4K") in the vi.json file to the Vietnamese strings.
| "Sub-mode": "Sub-mode", | ||
| "1K price": "1K price", | ||
| "2K price": "2K price", | ||
| "4K price": "4K price", | ||
| "1K (≤ 1024px)": "1K (≤ 1024px)", | ||
| "2K (≤ 2048px)": "2K (≤ 2048px)", | ||
| "4K (> 2048px)": "4K (> 2048px)", |
There was a problem hiding this comment.
Translate newly added zh locale values instead of leaving English fallbacks.
Lines 2901-2907 are still English in zh.json, so Chinese users will see mixed-language UI for the new per-resolution billing fields.
💡 Suggested fix
- "Sub-mode": "Sub-mode",
- "1K price": "1K price",
- "2K price": "2K price",
- "4K price": "4K price",
- "1K (≤ 1024px)": "1K (≤ 1024px)",
- "2K (≤ 2048px)": "2K (≤ 2048px)",
- "4K (> 2048px)": "4K (> 2048px)",
+ "Sub-mode": "子模式",
+ "1K price": "1K 价格",
+ "2K price": "2K 价格",
+ "4K price": "4K 价格",
+ "1K (≤ 1024px)": "1K(≤ 1024px)",
+ "2K (≤ 2048px)": "2K(≤ 2048px)",
+ "4K (> 2048px)": "4K(> 2048px)",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "Sub-mode": "Sub-mode", | |
| "1K price": "1K price", | |
| "2K price": "2K price", | |
| "4K price": "4K price", | |
| "1K (≤ 1024px)": "1K (≤ 1024px)", | |
| "2K (≤ 2048px)": "2K (≤ 2048px)", | |
| "4K (> 2048px)": "4K (> 2048px)", | |
| "Sub-mode": "子模式", | |
| "1K price": "1K 价格", | |
| "2K price": "2K 价格", | |
| "4K price": "4K 价格", | |
| "1K (≤ 1024px)": "1K(≤ 1024px)", | |
| "2K (≤ 2048px)": "2K(≤ 2048px)", | |
| "4K (> 2048px)": "4K(> 2048px)", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/zh.json` around lines 2901 - 2907, The new
zh.json entries ("Sub-mode", "1K price", "2K price", "4K price", "1K (≤
1024px)", "2K (≤ 2048px)", "4K (> 2048px)") are still in English; replace each
value with an appropriate Chinese translation (e.g., translate "Sub-mode" to
"子模式", "1K price" to "1K 价格", "2K price" to "2K 价格", "4K price" to "4K 价格", and
localize the resolution labels like "1K (≤ 1024px)" -> "1K(≤1024px)", etc.) so
the zh locale has no English fallbacks.
…ils dialog Signed-off-by: Micah-Zheng <102610064+Micah-Zheng@users.noreply.github.com>
问题描述
部分图片模型或上游聚合渠道更适合按输出分辨率计费,而不是只按 token 倍率或固定单次价格计费。当前模型定价页只能配置按 token、固定单次价格或表达式计费,无法为图片生成模型设置 1K / 2K / 4K 这类按分辨率档位的固定图片价格。
修复方案
image_model_setting.models配置,用于为图片模型选择token或per_size计费模式,并保存 1K / 2K / 4K 的每张图片价格。per_size模式下按「单价 × 图片数量 × 分组倍率 × QuotaPerUnit」结算。ModelPrice=0哨兵值与image_model_setting.models。行为说明
token模式,现有模型计费行为不变。per_size后,实际结算使用按分辨率价格,不再叠加普通 token / tiered expression 计费,避免双重扣费。auto或无法识别,默认按 2K 档位计费。image_model_setting配置,避免旧配置继续影响后端扣费。影响范围
other字段。相比上一版本的改动(CodeRabbit review 修复)
model-pricing-sheet: 切换到「新增模型」时补充重置perRequestSubMode/price1k/price2k/price4k,避免旧值泄漏进新模型model-pricing-sheet: 将 Sub-mode / 1K price / 2K price / 4K price 及分辨率档位 label 统一走t()model-ratio-visual-editor:React.memocomparator 补充imageModelSetting比较,避免 prop 更新被忽略model-ratio-visual-editor: 将safeJsonParse(imageModelSetting)提到.map()循环外,避免重复解析ratio-settings-card:ImageModelSettingzod schema 增加 JSON 格式验证ratio-settings-card:modelNormalizedDefaults和saveModelRatios中对ImageModelSetting统一走normalizeJsonString,避免格式差异导致误判 dirtyimage_model_setting.go:parsePositiveInt增加整数溢出保护(上限 65536)zh.json:auth.resetPasswordConfirm.retry单位从英文s改为秒验证
git diff --checkcd web/default && bun run i18n:synccd web/default && bun run typecheck✅备注
本地环境没有
go/gofmt命令,因此未在本机运行 Go 测试或 gofmt。Summary by CodeRabbit
New Features
Improvements
Localization