This commit is contained in:
2026-08-16 16:57:36 +02:00
parent 96f8c1115c
commit 532d439c41
106 changed files with 10147 additions and 494 deletions
+55 -1
View File
@@ -1,5 +1,7 @@
/**
* Admin orgs UI client — users + companies directory, staff roles, plan assign.
* Admin orgs UI client — users + companies directory, staff roles, plan assign,
* clone-catalog, and Fix A1 (`POST …/fix-catalog`).
* Flash Fix A1: result.prompts / hashes / categories → flash.admin.fixA1Success.
* Contract: docs/admin-roles-support/04-contract.md · Docs: 10-admin-orgs-ui.md
*/
import { api, ApiError } from "$lib/api";
@@ -18,6 +20,8 @@ export const ADMIN_USERS_PATH = "/api/admin/users";
export const ADMIN_COMPANIES_PATH = "/api/admin/companies";
export const ADMIN_CLONE_CATALOG_PATH = (companyId: string) =>
`${ADMIN_COMPANIES_PATH}/${encodeURIComponent(companyId)}/clone-catalog`;
export const ADMIN_FIX_CATALOG_PATH = (companyId: string) =>
`${ADMIN_COMPANIES_PATH}/${encodeURIComponent(companyId)}/fix-catalog`;
export const ADMIN_STAFF_ROLE_PATH = (userId: string) =>
`/api/admin/users/${encodeURIComponent(userId)}/staff-role`;
@@ -226,6 +230,56 @@ export async function cloneAdminCompanyCatalog(
});
}
export type FixCatalogResult = {
company_id: string;
company_name: string;
a1_cohort?: boolean;
category_attribute_orphans_removed?: number;
category_attribute_links?: number;
/** Alias of category_prompts_updated for flash.admin.fixA1Success {prompts}. */
prompts?: number;
category_prompts_updated?: number;
product_enhance_languages?: number;
/** Alias of weak_hashes_cleared for flash {hashes}. */
hashes?: number;
weak_hashes_cleared?: number;
/** Alias of categories_backfilled for flash {categories}. */
categories?: number;
categories_backfilled?: number;
attributes_sanitized?: number;
products_scanned?: number;
reprocess_needed_count?: number;
reprocess_sample_raw_product_ids?: string[];
};
export type FixCatalogResponse = {
status: string;
result: FixCatalogResult;
note?: string;
};
/** In-place Fix A1 / catalog hygiene. Never clears catalog; no mass reprocess. */
export async function fixAdminCompanyCatalog(
companyId: string,
opts?: { backfillCategories?: boolean; reprocessSampleLimit?: number }
): Promise<FixCatalogResponse> {
const body: {
confirm: true;
backfill_categories?: boolean;
reprocess_sample_limit?: number;
} = { confirm: true };
if (opts?.backfillCategories === false) {
body.backfill_categories = false;
}
if (typeof opts?.reprocessSampleLimit === "number") {
body.reprocess_sample_limit = opts.reprocessSampleLimit;
}
return api<FixCatalogResponse>(ADMIN_FIX_CATALOG_PATH(companyId), {
method: "POST",
body
});
}
export function isStaffRoleApiUnavailable(err: unknown): boolean {
return err instanceof ApiError && (err.status === 404 || err.status === 501);
}
@@ -33,6 +33,7 @@
productFeedSyncLabel,
productHasEprel,
productHasSpecs,
productImageUrls,
resolveOriginalDescription,
resolveOriginalName
} from "./types";
@@ -438,6 +439,7 @@
const coverageCount = $derived(
enrichment ? Object.values(enrichment).filter((s) => s !== "missing").length : 0
);
const imageUrls = $derived.by(() => productImageUrls(product));
function attrDisplayValue(key: string, value: string): string {
if (key.trim().toLowerCase() !== "category") return value;
@@ -571,8 +573,9 @@
primary={contentLanguages[0] || DEFAULT_CONTENT_LANGUAGE}
allowAdd={false}
hasOverride={(code) => {
const f = fieldsForLang(code);
return Boolean(f.processedName.trim() || f.processedDescription.trim());
const primary = contentLanguages[0] || DEFAULT_CONTENT_LANGUAGE;
if (code === primary) return false;
return Boolean(product?.localized_content?.[code]);
}}
onChange={(code) => applyContentLang(code)}
/>
@@ -890,8 +893,36 @@
</select>
</div>
</div>
</section>
</TabsContent>
</section>
{#if imageUrls.length > 0}
<section class="space-y-3" data-testid="product-images">
<h3 class="text-sm font-medium">{i18n.t("products.edit.images")}</h3>
<div class="flex flex-wrap gap-3">
{#each imageUrls as src, i}
<a
href={src}
target="_blank"
rel="noopener noreferrer"
class="block overflow-hidden rounded-md border border-border bg-muted/30"
title={src}
>
<img
src={src}
alt={i18n.t("products.edit.imageAlt", {
index: i + 1,
name: product?.processed_name || product?.name || productId
})}
class="h-28 w-28 object-contain"
loading="lazy"
referrerpolicy="no-referrer"
/>
</a>
{/each}
</div>
</section>
{/if}
</TabsContent>
<TabsContent value="feed" class="mt-4 space-y-4">
<section class="rounded-md border border-border px-3 py-3">
@@ -957,14 +988,28 @@
>{field.value}</pre>
{/if}
{:else if /^https?:\/\//i.test(field.value)}
<a
href={field.value}
target="_blank"
rel="noopener noreferrer"
class="break-all text-primary underline-offset-2 hover:underline"
>
{field.value}
</a>
{@const isImage =
/image|img|photo|picture|thumbnail/i.test(field.key) ||
/\.(jpe?g|png|gif|webp|svg|avif)(\?|$)/i.test(field.value)}
<div class="space-y-2">
{#if isImage}
<img
src={field.value}
alt={field.key}
class="max-h-40 max-w-full rounded border border-border object-contain"
loading="lazy"
referrerpolicy="no-referrer"
/>
{/if}
<a
href={field.value}
target="_blank"
rel="noopener noreferrer"
class="break-all text-primary underline-offset-2 hover:underline"
>
{field.value}
</a>
</div>
{:else}
<span class="break-words {field.ok ? '' : 'text-chart-amber'}">{field.value}</span>
{/if}
@@ -847,6 +847,39 @@ function digAttrBag(
return undefined;
}
/** Collect http(s) image URLs from mapped_data (main_image + more_images). */
export function productImageUrls(product: ProductRow | null | undefined): string[] {
const mapped = asRecord(product?.mapped_data);
if (!mapped) return [];
const out: string[] = [];
const seen = new Set<string>();
const push = (raw: unknown) => {
if (typeof raw === "string") {
const url = raw.trim();
if (!/^https?:\/\//i.test(url) || seen.has(url)) return;
seen.add(url);
out.push(url);
return;
}
if (Array.isArray(raw)) {
for (const item of raw) push(item);
return;
}
const rec = asRecord(raw);
if (!rec) return;
for (const key of ["url", "src", "href", "link"]) {
if (rec[key] != null) push(rec[key]);
}
};
for (const key of ["main_image", "mainImage", "image", "image_url", "imageUrl"]) {
if (mapped[key] != null) push(mapped[key]);
}
for (const key of ["more_images", "moreImages", "images", "additional_images"]) {
if (mapped[key] != null) push(mapped[key]);
}
return out;
}
/** True when product carries an EPREL id or enriched energy-label payload. */
export function productHasEprel(product: ProductRow | null | undefined): boolean {
if (!product) return false;
+51
View File
@@ -0,0 +1,51 @@
/**
* Focused check: admin Fix A1 i18n keys exist in every UI_LOCALES pack.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { UI_LOCALES } from "./locales.ts";
import { en } from "./messages/en.ts";
import { loadAllMessages, messagesFor } from "./messages/catalog.ts";
import { sl } from "./messages/sl.ts";
const FIX_A1_KEYS = [
"admin.users.fixA1",
"admin.users.fixA1Aria",
"admin.users.fixA1Title",
"admin.users.fixA1Desc",
"admin.users.fixA1Company",
"admin.users.fixA1Warning",
"admin.users.fixA1Cancel",
"admin.users.fixA1Confirm",
"flash.admin.fixA1Success",
"flash.admin.fixA1Error"
] as const;
describe("admin Fix A1 i18n keys", () => {
it("English defines every Fix A1 key", () => {
for (const key of FIX_A1_KEYS) {
assert.equal(typeof en[key], "string", key);
assert.ok(en[key].trim().length > 0, key);
}
});
it("every registered UI locale has Fix A1 keys", async () => {
await loadAllMessages();
for (const { code } of UI_LOCALES) {
const pack = code === "en" ? en : messagesFor(code);
for (const key of FIX_A1_KEYS) {
const value = pack[key];
assert.equal(typeof value, "string", `${code}:${key}`);
assert.ok(String(value).trim().length > 0, `${code}:${key}`);
}
}
});
it("Slovenian ready pack has Fix A1 keys (not in UI_LOCALES)", () => {
for (const key of FIX_A1_KEYS) {
assert.equal(typeof sl[key], "string", key);
assert.ok(sl[key].trim().length > 0, key);
}
});
});
+10
View File
@@ -812,6 +812,14 @@ export const de: MessageDict = {
"admin.users.cloneCatalogCancel": "Abbrechen",
"admin.users.cloneCatalogConfirm": "Katalog kopieren",
"admin.users.cloneDestFallback": "Ihr Sandbox-Unternehmen",
"admin.users.fixA1": "A1-Katalog reparieren",
"admin.users.fixA1Aria": "KI-Prompts, Kategorien und Enhance-Hashes für {name} reparieren",
"admin.users.fixA1Title": "Unternehmenskatalog reparieren",
"admin.users.fixA1Desc": "Wendet korrigierte KI-Prompts erneut an, löst schwache Enhance-Hashes und füllt Kategorien aus mapped_data nach.",
"admin.users.fixA1Company": "Unternehmen: {name}",
"admin.users.fixA1Warning": "Bevorzugen Sie Platform Demo oder ein explizites Unternehmen. Löscht keine Feeds, Mappings oder Rohprodukte. Schützt A1-Kohorten-Überschreibregeln.",
"admin.users.fixA1Cancel": "Abbrechen",
"admin.users.fixA1Confirm": "Katalog reparieren",
"admin.users.noUsers": "Keine Benutzer entsprechen diesem Filter.",
"admin.users.noCompanies": "Keine Unternehmen entsprechen diesem Filter.",
"admin.users.assignRoleTitle": "Mitarbeiterrolle zuweisen",
@@ -2297,6 +2305,8 @@ export const de: MessageDict = {
"flash.admin.staffRoleUnavailable": "Mitarbeiterrollen-Updates sind auf diesem Server noch nicht verfügbar.",
"flash.admin.planAssigned": "Plan {name} zugewiesen.",
"flash.admin.catalogCloned": "{source} nach {dest} kopiert: {products} Produkte, {categories} Kategorien.",
"flash.admin.fixA1Success": "Katalogreparatur für {name} abgeschlossen: {prompts} Prompts aktualisiert, {hashes} Hashes gelöscht, {categories} Kategorien nachgefüllt.",
"flash.admin.fixA1Error": "Katalogreparatur für {name} fehlgeschlagen.",
"flash.admin.planAssignedShort": "Plan zugewiesen.",
"flash.admin.creditsUpdated": "Credits aktualisiert.",
"flash.admin.cyclesProcessed": "Abrechnungszyklen verarbeitet: {count}",
+16
View File
@@ -355,6 +355,10 @@ export const en: MessageDict = {
"dashboard.lowCreditsTitle": "Credits running low",
"dashboard.lowCreditsMessage": "{remaining} of {total} credits left. Top up or upgrade before jobs stall.",
"dashboard.latestJobs": "Latest processing jobs",
"dashboard.jobsRunning": "{count} job running",
"dashboard.jobsRunningPlural": "{count} jobs running",
"dashboard.activeJobs": "{count} active job",
"dashboard.activeJobsPlural": "{count} active jobs",
"dashboard.noJobsEmpty": "No jobs yet — import products first.",
"dashboard.noJobsReady": "No recent jobs. Start one from Products when you are ready.",
"dashboard.startJob": "Start a job",
@@ -835,6 +839,14 @@ export const en: MessageDict = {
"admin.users.cloneCatalogCancel": "Cancel",
"admin.users.cloneCatalogConfirm": "Copy catalog",
"admin.users.cloneDestFallback": "your sandbox company",
"admin.users.fixA1": "Fix A1 catalog",
"admin.users.fixA1Aria": "Repair AI prompts, categories, and enhance hashes for {name}",
"admin.users.fixA1Title": "Fix company catalog",
"admin.users.fixA1Desc": "In-place repair: category attribute links, category enhance prompts (with attrs), weak enhance hashes, optional category backfill, attribute sanitize. No mass reprocess.",
"admin.users.fixA1Company": "Company: {name}",
"admin.users.fixA1Warning": "Prefer Platform Demo; A1 may be targeted in place with confirm. Never clears the catalog or uses A1 as a clone destination.",
"admin.users.fixA1Cancel": "Cancel",
"admin.users.fixA1Confirm": "Fix catalog",
"admin.users.noUsers": "No users match this filter.",
"admin.users.noCompanies": "No companies match this filter.",
"admin.users.assignRoleTitle": "Assign staff role",
@@ -2322,6 +2334,8 @@ export const en: MessageDict = {
"flash.admin.staffRoleUnavailable": "Staff role updates are not available on this server yet.",
"flash.admin.planAssigned": "Plan assigned to {name}.",
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} categories. Open Products and process catalog GTINs (EAN-only API calls need a matching feed product).",
"flash.admin.fixA1Success": "Catalog repair finished for {name}: {prompts} category prompts updated, {hashes} hashes cleared, {categories} categories backfilled.",
"flash.admin.fixA1Error": "Catalog repair failed for {name}.",
"flash.admin.planAssignedShort": "Plan assigned.",
"flash.admin.creditsUpdated": "Credits updated.",
"flash.admin.cyclesProcessed": "Billing cycles processed: {count}",
@@ -4238,6 +4252,8 @@ export const en: MessageDict = {
"products.edit.updatedRelative": "· updated {when}",
"products.edit.viewAllFeedFields": "View all feed fields ({filled}/{total})",
"products.edit.productIdentity": "Product identity",
"products.edit.images": "Images",
"products.edit.imageAlt": "Product image {index}: {name}",
"products.edit.aiProcessedName": "AI-processed name",
"products.edit.originalName": "Original name",
"products.edit.productIdSku": "Product ID / SKU",
+10
View File
@@ -812,6 +812,14 @@ export const es: MessageDict = {
"admin.users.cloneCatalogCancel": "Cancelar",
"admin.users.cloneCatalogConfirm": "Copiar catálogo",
"admin.users.cloneDestFallback": "tu empresa sandbox",
"admin.users.fixA1": "Reparar catálogo A1",
"admin.users.fixA1Aria": "Reparar prompts de IA, categorías y hashes de enhance para {name}",
"admin.users.fixA1Title": "Reparar catálogo de la empresa",
"admin.users.fixA1Desc": "Vuelve a aplicar prompts de IA corregidos, limpia hashes de enhance débiles y completa categorías desde mapped_data.",
"admin.users.fixA1Company": "Empresa: {name}",
"admin.users.fixA1Warning": "Prefiera Platform Demo o una empresa explícita. No elimina feeds, mapeos ni productos en bruto. Protege las reglas de sobrescritura de la cohorte A1.",
"admin.users.fixA1Cancel": "Cancelar",
"admin.users.fixA1Confirm": "Reparar catálogo",
"admin.users.noUsers": "Ningún usuario coincide con este filtro.",
"admin.users.noCompanies": "Ninguna empresa coincide con este filtro.",
"admin.users.assignRoleTitle": "Asignar rol de personal",
@@ -2296,6 +2304,8 @@ export const es: MessageDict = {
"flash.admin.staffRoleUpdated": "Rol de personal actualizado para {email}.",
"flash.admin.staffRoleUnavailable": "Las actualizaciones de rol de personal aún no están disponibles en este servidor.",
"flash.admin.catalogCloned": "Se copió {source} en {dest}: {products} productos, {categories} categorías.",
"flash.admin.fixA1Success": "Reparación del catálogo de {name} finalizada: {prompts} prompts actualizados, {hashes} hashes borrados, {categories} categorías rellenadas.",
"flash.admin.fixA1Error": "Falló la reparación del catálogo de {name}.",
"flash.admin.planAssigned": "Plan asignado a {name}.",
"flash.admin.planAssignedShort": "Plan asignado.",
"flash.admin.creditsUpdated": "Créditos actualizados.",
+10
View File
@@ -812,6 +812,14 @@ export const fr: MessageDict = {
"admin.users.cloneCatalogCancel": "Annuler",
"admin.users.cloneCatalogConfirm": "Copier le catalogue",
"admin.users.cloneDestFallback": "votre entreprise sandbox",
"admin.users.fixA1": "Réparer le catalogue A1",
"admin.users.fixA1Aria": "Réparer les prompts IA, catégories et hashes enhance pour {name}",
"admin.users.fixA1Title": "Réparer le catalogue entreprise",
"admin.users.fixA1Desc": "Réapplique les prompts IA corrigés, efface les hashes enhance faibles et complète les catégories depuis mapped_data.",
"admin.users.fixA1Company": "Entreprise : {name}",
"admin.users.fixA1Warning": "Préférer Platform Demo ou une entreprise explicite. Ne supprime ni feeds, ni mappings, ni produits bruts. Protège les règles d'écrasement de la cohorte A1.",
"admin.users.fixA1Cancel": "Annuler",
"admin.users.fixA1Confirm": "Réparer le catalogue",
"admin.users.noUsers": "Aucun utilisateur ne correspond à ce filtre.",
"admin.users.noCompanies": "Aucune entreprise ne correspond à ce filtre.",
"admin.users.assignRoleTitle": "Assigner un rôle du personnel",
@@ -2296,6 +2304,8 @@ export const fr: MessageDict = {
"flash.admin.staffRoleUpdated": "Rôle du personnel mis à jour pour {email}.",
"flash.admin.staffRoleUnavailable": "Les mises à jour de rôle personnel ne sont pas encore disponibles sur ce serveur.",
"flash.admin.catalogCloned": "{source} copié vers {dest} : {products} produits, {categories} catégories.",
"flash.admin.fixA1Success": "Réparation du catalogue pour {name} terminée : {prompts} prompts mis à jour, {hashes} hashes effacés, {categories} catégories complétées.",
"flash.admin.fixA1Error": "Échec de la réparation du catalogue pour {name}.",
"flash.admin.planAssigned": "Offre assignée à {name}.",
"flash.admin.planAssignedShort": "Offre assignée.",
"flash.admin.creditsUpdated": "Crédits mis à jour.",
+10
View File
@@ -812,6 +812,14 @@ export const it: MessageDict = {
"admin.users.cloneCatalogCancel": "Annulla",
"admin.users.cloneCatalogConfirm": "Copia catalogo",
"admin.users.cloneDestFallback": "la tua azienda sandbox",
"admin.users.fixA1": "Ripara catalogo A1",
"admin.users.fixA1Aria": "Ripara prompt IA, categorie e hash enhance per {name}",
"admin.users.fixA1Title": "Ripara catalogo azienda",
"admin.users.fixA1Desc": "Riapplica i prompt IA corretti, cancella hash enhance deboli e completa le categorie da mapped_data.",
"admin.users.fixA1Company": "Azienda: {name}",
"admin.users.fixA1Warning": "Preferisci Platform Demo o un'azienda esplicita. Non elimina feed, mapping o prodotti grezzi. Protegge le regole di sovrascrittura della coorte A1.",
"admin.users.fixA1Cancel": "Annulla",
"admin.users.fixA1Confirm": "Ripara catalogo",
"admin.users.noUsers": "Nessun utente corrisponde a questo filtro.",
"admin.users.noCompanies": "Nessuna azienda corrisponde a questo filtro.",
"admin.users.assignRoleTitle": "Assegna ruolo staff",
@@ -2296,6 +2304,8 @@ export const it: MessageDict = {
"flash.admin.staffRoleUpdated": "Ruolo staff aggiornato per {email}.",
"flash.admin.staffRoleUnavailable": "Gli aggiornamenti del ruolo staff non sono ancora disponibili su questo server.",
"flash.admin.catalogCloned": "Copiato {source} in {dest}: {products} prodotti, {categories} categorie.",
"flash.admin.fixA1Success": "Riparazione catalogo per {name} completata: {prompts} prompt aggiornati, {hashes} hash cancellati, {categories} categorie integrate.",
"flash.admin.fixA1Error": "Riparazione catalogo per {name} non riuscita.",
"flash.admin.planAssigned": "Piano assegnato a {name}.",
"flash.admin.planAssignedShort": "Piano assegnato.",
"flash.admin.creditsUpdated": "Crediti aggiornati.",
+10
View File
@@ -812,6 +812,14 @@ export const ja: MessageDict = {
"admin.users.cloneCatalogCancel": "キャンセル",
"admin.users.cloneCatalogConfirm": "カタログをコピー",
"admin.users.cloneDestFallback": "サンドボックス会社",
"admin.users.fixA1": "A1カタログを修復",
"admin.users.fixA1Aria": "{name} のAIプロンプト、カテゴリ、enhanceハッシュを修復",
"admin.users.fixA1Title": "会社カタログを修復",
"admin.users.fixA1Desc": "修正済みAIプロンプトを再適用し、弱いenhanceハッシュを消去し、mapped_dataからカテゴリを補完します。",
"admin.users.fixA1Company": "会社: {name}",
"admin.users.fixA1Warning": "Platform Demoまたは明示的な会社を優先してください。フィード、マッピング、生製品は削除しません。A1コホートの上書きルールを保護します。",
"admin.users.fixA1Cancel": "キャンセル",
"admin.users.fixA1Confirm": "カタログを修復",
"admin.users.noUsers": "このフィルタに一致するユーザーはいません。",
"admin.users.noCompanies": "このフィルタに一致する会社はありません。",
"admin.users.assignRoleTitle": "スタッフロールを割り当て",
@@ -2296,6 +2304,8 @@ export const ja: MessageDict = {
"flash.admin.staffRoleUpdated": "{email} のスタッフロールを更新しました。",
"flash.admin.staffRoleUnavailable": "このサーバーではスタッフロールの更新はまだ利用できません。",
"flash.admin.catalogCloned": "{source} を {dest} にコピーしました: 商品 {products}、カテゴリ {categories}。",
"flash.admin.fixA1Success": "{name} のカタログ修復が完了: プロンプト {prompts} 件更新、ハッシュ {hashes} 件消去、カテゴリ {categories} 件補完。",
"flash.admin.fixA1Error": "{name} のカタログ修復に失敗しました。",
"flash.admin.planAssigned": "{name} にプランを割り当てました。",
"flash.admin.planAssignedShort": "プランを割り当てました。",
"flash.admin.creditsUpdated": "クレジットを更新しました。",
+10
View File
@@ -812,6 +812,14 @@ export const nl: MessageDict = {
"admin.users.cloneCatalogCancel": "Annuleren",
"admin.users.cloneCatalogConfirm": "Catalogus kopiëren",
"admin.users.cloneDestFallback": "je sandboxbedrijf",
"admin.users.fixA1": "A1-catalogus repareren",
"admin.users.fixA1Aria": "AI-prompts, categorieën en enhance-hashes voor {name} repareren",
"admin.users.fixA1Title": "Bedrijfscatalogus repareren",
"admin.users.fixA1Desc": "Past gecorrigeerde AI-prompts opnieuw toe, wist zwakke enhance-hashes en vult categorieën bij vanuit mapped_data.",
"admin.users.fixA1Company": "Bedrijf: {name}",
"admin.users.fixA1Warning": "Geef voorkeur aan Platform Demo of een expliciet bedrijf. Verwijdert geen feeds, mappings of ruwe producten. Beschermt A1-cohort-overschrijfregels.",
"admin.users.fixA1Cancel": "Annuleren",
"admin.users.fixA1Confirm": "Catalogus repareren",
"admin.users.noUsers": "Geen gebruikers komen overeen met dit filter.",
"admin.users.noCompanies": "Geen bedrijven komen overeen met dit filter.",
"admin.users.assignRoleTitle": "Medewerkerrol toewijzen",
@@ -2296,6 +2304,8 @@ export const nl: MessageDict = {
"flash.admin.staffRoleUpdated": "Personeelsrol bijgewerkt voor {email}.",
"flash.admin.staffRoleUnavailable": "Personeelsrolupdates zijn op deze server nog niet beschikbaar.",
"flash.admin.catalogCloned": "{source} gekopieerd naar {dest}: {products} producten, {categories} categorieën.",
"flash.admin.fixA1Success": "Catalogusreparatie voor {name} voltooid: {prompts} prompts bijgewerkt, {hashes} hashes gewist, {categories} categorieën aangevuld.",
"flash.admin.fixA1Error": "Catalogusreparatie voor {name} mislukt.",
"flash.admin.planAssigned": "Plan toegewezen aan {name}.",
"flash.admin.planAssignedShort": "Plan toegewezen.",
"flash.admin.creditsUpdated": "Credits bijgewerkt.",
+10
View File
@@ -812,6 +812,14 @@ export const pl: MessageDict = {
"admin.users.cloneCatalogCancel": "Anuluj",
"admin.users.cloneCatalogConfirm": "Kopiuj katalog",
"admin.users.cloneDestFallback": "twoja firma sandbox",
"admin.users.fixA1": "Napraw katalog A1",
"admin.users.fixA1Aria": "Napraw prompty AI, kategorie i hashe enhance dla {name}",
"admin.users.fixA1Title": "Napraw katalog firmy",
"admin.users.fixA1Desc": "Ponownie stosuje poprawione prompty AI, czyści słabe hashe enhance i uzupełnia kategorie z mapped_data.",
"admin.users.fixA1Company": "Firma: {name}",
"admin.users.fixA1Warning": "Preferuj Platform Demo lub wskazaną firmę. Nie usuwa feedów, mapowań ani surowych produktów. Chroni reguły nadpisywania kohorty A1.",
"admin.users.fixA1Cancel": "Anuluj",
"admin.users.fixA1Confirm": "Napraw katalog",
"admin.users.noUsers": "Żaden użytkownik nie pasuje do tego filtra.",
"admin.users.noCompanies": "Żadna firma nie pasuje do tego filtra.",
"admin.users.assignRoleTitle": "Przypisz rolę personelu",
@@ -2296,6 +2304,8 @@ export const pl: MessageDict = {
"flash.admin.staffRoleUpdated": "Zaktualizowano rolę personelu dla {email}.",
"flash.admin.staffRoleUnavailable": "Aktualizacje ról personelu nie są jeszcze dostępne na tym serwerze.",
"flash.admin.catalogCloned": "Skopiowano {source} do {dest}: {products} produktów, {categories} kategorii.",
"flash.admin.fixA1Success": "Naprawa katalogu dla {name} zakończona: zaktualizowano {prompts} promptów, wyczyszczono {hashes} hashy, uzupełniono {categories} kategorii.",
"flash.admin.fixA1Error": "Naprawa katalogu dla {name} nie powiodła się.",
"flash.admin.planAssigned": "Przypisano plan do {name}.",
"flash.admin.planAssignedShort": "Przypisano plan.",
"flash.admin.creditsUpdated": "Zaktualizowano kredyty.",
+10
View File
@@ -812,6 +812,14 @@ export const pt: MessageDict = {
"admin.users.cloneCatalogCancel": "Cancelar",
"admin.users.cloneCatalogConfirm": "Copiar catálogo",
"admin.users.cloneDestFallback": "a sua empresa sandbox",
"admin.users.fixA1": "Reparar catálogo A1",
"admin.users.fixA1Aria": "Reparar prompts de IA, categorias e hashes de enhance para {name}",
"admin.users.fixA1Title": "Reparar catálogo da empresa",
"admin.users.fixA1Desc": "Reaplica prompts de IA corrigidos, limpa hashes de enhance fracos e preenche categorias a partir de mapped_data.",
"admin.users.fixA1Company": "Empresa: {name}",
"admin.users.fixA1Warning": "Prefira Platform Demo ou uma empresa explícita. Não elimina feeds, mapeamentos ou produtos brutos. Protege as regras de substituição da coorte A1.",
"admin.users.fixA1Cancel": "Cancelar",
"admin.users.fixA1Confirm": "Reparar catálogo",
"admin.users.noUsers": "Nenhum utilizador corresponde a este filtro.",
"admin.users.noCompanies": "Nenhuma empresa corresponde a este filtro.",
"admin.users.assignRoleTitle": "Atribuir função de equipa",
@@ -2296,6 +2304,8 @@ export const pt: MessageDict = {
"flash.admin.staffRoleUpdated": "Função de pessoal atualizada para {email}.",
"flash.admin.staffRoleUnavailable": "As atualizações de função de pessoal ainda não estão disponíveis neste servidor.",
"flash.admin.catalogCloned": "Copiado {source} para {dest}: {products} produtos, {categories} categorias.",
"flash.admin.fixA1Success": "Reparação do catálogo de {name} concluída: {prompts} prompts atualizados, {hashes} hashes limpos, {categories} categorias preenchidas.",
"flash.admin.fixA1Error": "Falha na reparação do catálogo de {name}.",
"flash.admin.planAssigned": "Plano atribuído a {name}.",
"flash.admin.planAssignedShort": "Plano atribuído.",
"flash.admin.creditsUpdated": "Créditos atualizados.",
+15
View File
@@ -0,0 +1,15 @@
import type { MessageDict } from "./types.ts";
/** Slovenian (sl) UI strings for admin Fix A1 — ready pack. Not registered in UI_LOCALES yet (avoid inventing locale switcher). */
export const sl: MessageDict = {
"admin.users.fixA1": "Popravi katalog A1",
"admin.users.fixA1Aria": "Popravi AI pozive, kategorije in zgoščevalne vrednosti za {name}",
"admin.users.fixA1Title": "Popravi katalog podjetja",
"admin.users.fixA1Desc": "Ponovno uporabi popravljene AI pozive, počisti šibke enhance zgoščevalne vrednosti in dopolni kategorije iz mapped podatkov.",
"admin.users.fixA1Company": "Podjetje: {name}",
"admin.users.fixA1Warning": "Raje Platform Demo ali izrecno podjetje. Ne briše feedov, mapiranj ali surovih izdelkov. Ščiti pravila prepisovanja A1 kohorte.",
"admin.users.fixA1Cancel": "Prekliči",
"admin.users.fixA1Confirm": "Popravi katalog",
"flash.admin.fixA1Success": "Popravilo kataloga za {name} končano: posodobljenih pozivov {prompts}, počiščenih zgoščevalnih vrednosti {hashes}, dopolnjenih kategorij {categories}.",
"flash.admin.fixA1Error": "Popravilo kataloga za {name} ni uspelo.",
};