This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch fix/editor-source-default-remote
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 82af241bc6b334b9187dc6404df4a3c1a76bfce4
Author: Wu Sheng <[email protected]>
AuthorDate: Tue May 26 21:24:06 2026 +0800

    fix(admin): default editor to remote + reset truly discards local draft
    
    Operator-facing fixes across the three admin editors (Layer
    Dashboards, Overview Templates, Translations):
    
    1. **Editor source default = remote** (Layer + Overview). The runtime
       bundle serves remote content to end users for synced / diverged /
       remote-only rows; the editor now opens from remote on every
       (re-)mount, with local draft winning when present and bundled
       only on bundled-fallback rows.
    
    2. **Source pill — three distinct states** (Layer + Overview).
       - Remote (default) → no pill.
       - After Reset to bundled (no local edits yet) → "from bundled".
       - After save / typed local edits → "from local".
       The bundled-vs-local distinction matters: reset stages bundled
       for inspection / push, local edits are typed content not on disk
       anywhere.
    
    3. **Source pill on Translations follows the same vocabulary.** As
       soon as the operator has a staged local draft for the current
       (template, locale), a "from local" chip surfaces in the editor
       header. Discard puts the editor back on the default (OAP overlay
       row) and the chip disappears.
    
    4. **Reset → Bundled stays visible even when synced** (Layer +
       Overview). Previously hidden via `v-if="!isSynced"`; now always
       rendered and disabled with a "(synced)" tail when bundled equals
       remote so the affordance stays visible while clearly signalling
       nothing meaningful to reset to.
    
    5. **Reset really discards** (Layer + Overview). "Reset to bundled" /
       "Reset to remote" advertised "Discard current edits …", but the
       implementation called `persistLocal(new_content)` after
       `loadFrom(src)` — writing the freshly-loaded content into
       localEdits as a brand-new draft and triggering the
       TemplateConflictPrompt right after every reset. Now calls
       `localEdits.remove(editName)`; subsequent edits re-create a
       draft naturally on the next change.
    
    6. **Picker filters auto-uncheck when their set empties** (Layer +
       Overview). Reset OAP to bundled / Push / Sync on the last row
       matching the active filter (e.g. the last Diverged layer) used to
       leave the operator stranded — the filter stuck on Diverged, the
       list went empty, and the checkbox itself was disabled because
       `divergedCount === 0`, so the operator couldn't uncheck to
       recover. Reactive guard watches both diverged + local counts and
       clears the matching filter when it goes to zero.
    
    Adds the new pill / dropdown labels + tooltips to all eight locale
    catalogs.
---
 .../admin/layer-templates/LayerDashboardsAdmin.vue | 128 ++++++++++++----
 .../overview-templates/OverviewTemplatesAdmin.vue  | 120 ++++++++++-----
 .../admin/translations/TranslationsView.vue        | 166 +++++++++++++++++++--
 apps/ui/src/i18n/locales/de.json                   |  24 ++-
 apps/ui/src/i18n/locales/en.json                   |  24 ++-
 apps/ui/src/i18n/locales/es.json                   |  24 ++-
 apps/ui/src/i18n/locales/fr.json                   |  24 ++-
 apps/ui/src/i18n/locales/ja.json                   |  24 ++-
 apps/ui/src/i18n/locales/ko.json                   |  24 ++-
 apps/ui/src/i18n/locales/pt.json                   |  24 ++-
 apps/ui/src/i18n/locales/zh-CN.json                |  24 ++-
 11 files changed, 519 insertions(+), 87 deletions(-)

diff --git 
a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue 
b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
index 3cd07d6..e1e4415 100644
--- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
+++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
@@ -171,6 +171,15 @@ function isDivergedRow(key: string): boolean {
 }
 const divergedCount = computed(() => templates.value.filter((t) => 
isDivergedRow(t.key)).length);
 const localCount = computed(() => templates.value.filter((t) => 
localEdits.has(layerEditName(t.key))).length);
+
+// Auto-uncheck the picker filter whenever its underlying set goes to
+// zero. Without this the operator can hit Reset / Push / Sync — which
+// drops the matching row out of the filter — and then be stranded on
+// an empty picker with no way to navigate to another layer (the
+// filter checkbox would also be disabled since divergedCount === 0).
+// Catches every path that depletes the set, not just reset.
+watch(divergedCount, (n) => { if (n === 0) divergedOnly.value = false; });
+watch(localCount, (n) => { if (n === 0) localOnly.value = false; });
 const filteredTemplates = computed<AdminLayerTemplate[]>(() => {
   const q = layerSearch.value.trim().toLowerCase();
   return templates.value.filter((t) => {
@@ -192,6 +201,13 @@ const localEdits = useLocalTemplateEdits();
 // Server-side bundled + remote content for the Reset-to / Preview editor
 // sources. Local (browser draft) comes from `localEdits`.
 const sources = useTemplateSources('layer');
+// Settled-state flag for the source pill: until the config-bundle
+// fetch resolves, `hasRemote(name)` returns false for every name, so
+// any pill we render reflects the boot fallback (bundled) and then
+// flips to its real value when the bundle lands — a visual flash on
+// every page open. The pill v-if's on `sourcesReady` so it stays
+// hidden until the data is real.
+const sourcesReady = computed(() => !sources.isLoading.value);
 const previewOverride = usePreviewOverride();
 
 async function loadAll(): Promise<void> {
@@ -214,7 +230,14 @@ async function loadAll(): Promise<void> {
     if (SCOPES.includes(queryScope as AdminScope)) {
       activeScope.value = queryScope as AdminScope;
     }
-    syncDraft();
+    // Seed the editor only once the config-bundle (and hence
+    // `remoteAvailable` / `hasLocalDraft`) has resolved — otherwise
+    // syncDraft falls through to bundled and the editor visibly
+    // re-loads as the bundle lands. Two paths: (a) sources are
+    // already ready (warm cache) → run synchronously; (b) still
+    // loading → handed off to the corrective watcher below, which
+    // fires syncDraft on the false → true transition.
+    if (sourcesReady.value) syncDraft();
   } catch (err) {
     error.value = err instanceof Error ? err.message : String(err);
   } finally {
@@ -253,7 +276,14 @@ watch(
  * is the baseline for `dirty` (= unsaved edits since the last load/save).
  * Saving always writes LOCAL; you then publish LOCAL → OAP.
  * ─────────────────────────────────────────────────────────────────── */
-const editorSource = ref<'local' | 'bundled' | 'remote'>('bundled');
+// Remote is the canonical baseline (what the runtime menu renders for
+// synced + diverged + remote-only rows). The editor opens from remote
+// on every (re-)mount and stays there until the operator explicitly
+// loads bundled (Reset to bundled) or saves a local draft. The default
+// `'remote'` here matches what `syncDraft()` will pick on the first
+// pass — pre-seeding the same value lets the source pill stay hidden
+// (labelling the default would be noise).
+const editorSource = ref<'local' | 'bundled' | 'remote'>('remote');
 const loadedSnapshot = ref<string>('');
 const editName = computed(() => layerEditName(selectedKey.value));
 const hasLocalDraft = computed(() => localEdits.has(editName.value));
@@ -293,22 +323,22 @@ function loadFrom(src: 'local' | 'bundled' | 'remote'): 
void {
   editorSource.value = src;
   saveMsg.value = null;
 }
-/** Seed the editor when the selected layer changes. Priority mirrors
- *  what the operator actually sees in the live menu / dashboards:
+/** Seed the editor when the selected layer changes. Remote is the
+ *  canonical baseline — it's what `pickLayerContent` in the runtime
+ *  bundle serves to end users for synced / diverged / remote-only
+ *  rows, so the editor opens from remote whenever remote is reachable.
+ *  Priority:
  *    1. Local draft — unpublished in-progress edits in this browser.
- *    2. Remote — when bundled and remote diverged (or the layer is
- *       remote-only), OAP is the source of truth and the runtime
- *       bundle loads it via `pickLayerContent` (bundle.ts). Showing
- *       bundled here would silently disagree with the live UI.
- *    3. Bundled — synced rows are byte-equal anyway; bundled-fallback
- *       is the bundle's only source. */
+ *    2. Remote — the default for every re-mount when remote exists.
+ *    3. Bundled — only when remote is absent (bundled-fallback). The
+ *       operator can also hit "Reset to bundled" explicitly to swap;
+ *       that path goes through `resetTo`, not this seed function. */
 function syncDraft(): void {
   if (hasLocalDraft.value) {
     loadFrom('local');
     return;
   }
-  const badge = sync.badgeFor(editName.value);
-  if ((badge === 'diverged' || badge === 'remote-only') && 
remoteAvailable.value) {
+  if (remoteAvailable.value) {
     loadFrom('remote');
     return;
   }
@@ -329,12 +359,17 @@ function persistLocal(content: AdminLayerTemplate): void {
   loadedSnapshot.value = JSON.stringify(content);
 }
 
-// "Reset to ▾" dropdown — reload the editor from a source AND adopt it as
-// the local draft (so the choice persists + Push/diff status updates).
+// "Reset to ▾" dropdown — discard the current local draft and reload the
+// editor from the picked source. The op-facing tooltip says "Discard
+// current edits and reload …", so the action must DROP the local draft
+// rather than re-stage the new content as a fresh draft (which the
+// previous implementation did via persistLocal, triggering the
+// TemplateConflictPrompt right after every reset). Subsequent edits in
+// the editor re-create a local draft naturally on the next change.
 const resetDropdownOpen = ref(false);
 function resetTo(src: 'bundled' | 'remote'): void {
   loadFrom(src);
-  if (draft.template) persistLocal(draft.template);
+  localEdits.remove(editName.value);
   resetDropdownOpen.value = false;
 }
 
@@ -387,7 +422,21 @@ function previewLive(src: 'local' | 'bundled' | 'remote'): 
void {
   window.open(href, '_blank', 'noopener');
 }
 
-watch(selectedKey, syncDraft);
+watch(selectedKey, () => {
+  if (sourcesReady.value) syncDraft();
+});
+
+// First-mount deferred sync. When the page opens on a cold cache
+// `loadAll()` returns before the config-bundle has settled, so the
+// initial syncDraft is skipped (see loadAll). The moment sources
+// transition to ready, run syncDraft for the currently-selected
+// layer — once. No clobber on later transitions: this only fires on
+// the false → true edge.
+watch(sourcesReady, (ready, wasReady) => {
+  if (ready && !wasReady && selectedKey.value) {
+    syncDraft();
+  }
+});
 onMounted(loadAll);
 // Force-refresh the cached config bundle on mount so per-row badges
 // (`synced` / `diverged` / `disabled`) reflect actual OAP state. Without
@@ -1599,13 +1648,29 @@ const namingTest = computed<NamingTestResult>(() => {
             </div>
             <div class="actions">
               <span v-if="saveMsg" class="save-msg">{{ saveMsg }}</span>
-              <!-- Where the editor content was loaded from — distinct from
-                   the sync-status chip by the title (prefixed "from "). -->
+              <!-- Source pill — three visible states, one per
+                   `editorSource`. The pill is gated on
+                   `sourcesReady` to suppress the flash on initial
+                   load: until the config-bundle settles we can't tell
+                   whether the row is remote-backed or bundled-only,
+                   so showing anything is a guess that flips moments
+                   later. Once the bundle resolves the right pill
+                   renders directly. -->
+              <span
+                v-if="sourcesReady && editorSource === 'local'"
+                class="src-tag is-local"
+                :title="t('Unpublished local edits — Push to publish to OAP.')"
+              >{{ t('from local') }}</span>
+              <span
+                v-else-if="sourcesReady && editorSource === 'bundled'"
+                class="src-tag is-bundled"
+                :title="t('Showing the shipped bundled default — Push to 
overwrite OAP with bundled.')"
+              >{{ t('from bundled') }}</span>
               <span
-                v-if="editorSource === 'local' || !isSynced"
-                class="src-tag"
-                :title="`Editing from: ${editorSource}`"
-              >from {{ editorSource }}</span>
+                v-else-if="sourcesReady && editorSource === 'remote'"
+                class="src-tag is-remote"
+                :title="t('Showing the OAP-live version. End users render the 
same bytes.')"
+              >{{ t('from remote') }}</span>
               <!-- Reset the editor to a source (discard current content). -->
               <div class="reset-dd">
                 <button class="sw-btn" type="button" @click="resetDropdownOpen 
= !resetDropdownOpen">
@@ -1614,23 +1679,32 @@ const namingTest = computed<NamingTestResult>(() => {
                 <template v-if="resetDropdownOpen">
                   <div class="reset-dd-backdrop" @click="resetDropdownOpen = 
false" />
                   <div class="reset-dd-pop">
+                    <!-- "Reset to → Bundled" stays visible even when
+                         the row is synced (bundled === remote). The
+                         button is disabled in that case with a
+                         "(synced)" tail so the operator can tell the
+                         option still exists but there's nothing
+                         meaningful to reset to — Bundled equals what
+                         Remote would already give them. -->
                     <button
-                      v-if="!isSynced"
                       class="reset-dd-item"
                       type="button"
-                      title="Discard current edits and reload the bundled 
(shipped) default."
+                      :disabled="isSynced"
+                      :title="isSynced
+                        ? t('Bundled equals OAP-live for this row — nothing to 
reset to.')
+                        : t('Discard current edits and reload the bundled 
(shipped) default.')"
                       @click="resetTo('bundled')"
                     >
-                      Bundled
+                      {{ t('Bundled') }}<span v-if="isSynced" 
class="reset-dd-suffix"> {{ t('(synced)') }}</span>
                     </button>
                     <button
                       class="reset-dd-item"
                       type="button"
                       :disabled="!remoteAvailable"
-                      :title="remoteAvailable ? 'Discard current edits and 
reload OAP\'s live version.' : 'OAP has no copy of this template yet.'"
+                      :title="remoteAvailable ? t('Discard current edits and 
reload OAP\'s live version.') : t('OAP has no copy of this template yet.')"
                       @click="resetTo('remote')"
                     >
-                      Remote
+                      {{ t('Remote') }}
                     </button>
                   </div>
                 </template>
diff --git 
a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue 
b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
index 353665e..81f2fd0 100644
--- a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
+++ b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
@@ -83,6 +83,10 @@ const router = useRouter();
 const localEdits = useLocalTemplateEdits();
 const previewOverride = usePreviewOverride();
 const sources = useTemplateSources('overview');
+// See LayerDashboardsAdmin: the source pill stays hidden until the
+// config-bundle settles so the initial pill state isn't a guess that
+// flips moments later.
+const sourcesReady = computed(() => !sources.isLoading.value);
 
 const OV_PREFIX = 'horizon.overview.';
 function summaryFrom(
@@ -165,6 +169,15 @@ function isDivergedRow(id: string): boolean {
 }
 const divergedCount = computed(() => dashboards.value.filter((d) => 
isDivergedRow(d.id)).length);
 const localDraftCount = computed(() => dashboards.value.filter((d) => 
hasLocalDraftFor(d.id)).length);
+
+// Auto-uncheck the picker filter when its underlying set drops to
+// zero. Without this the operator can hit Reset / Push / Sync — which
+// removes the matching row from the filtered set — and end up stuck
+// on an empty list with the checkbox itself disabled (since
+// divergedCount / localDraftCount === 0). Same guard as the Layer
+// Dashboards admin page.
+watch(divergedCount, (n) => { if (n === 0) divergedOnly.value = false; });
+watch(localDraftCount, (n) => { if (n === 0) localOnly.value = false; });
 const filteredDashboards = computed(() => {
   const q = listSearch.value.trim().toLowerCase();
   return dashboards.value.filter((d) => {
@@ -553,7 +566,13 @@ const isSynced = computed<boolean>(
 );
 
 const draft = ref<OverviewDashboard | null>(null);
-const editorSource = ref<'local' | 'bundled' | 'remote'>('bundled');
+// Remote is the canonical baseline. The editor opens from remote on
+// every (re-)mount (`defaultEditorSource` returns 'remote' whenever
+// remote is reachable + no local draft exists) and stays there until
+// the operator explicitly hits "Reset to bundled" or saves a local
+// draft. The initial ref matches so the source pill can stay hidden
+// on the default path.
+const editorSource = ref<'local' | 'bundled' | 'remote'>('remote');
 const loadedSnapshot = ref<string>('');
 
 function bundledContent(): OverviewDashboard | null {
@@ -587,12 +606,15 @@ function persistLocal(content: OverviewDashboard): void {
   loadedSnapshot.value = JSON.stringify(content);
 }
 
-// "Reset to ▾" dropdown — reload from a source AND adopt it as the local
-// draft (so the choice persists + Push/diff status updates).
+// "Reset to ▾" dropdown — discard the current local draft and reload
+// the editor from the picked source. Symmetric with the layer
+// dashboards editor: reset is "discard, reload", not "re-stage as a
+// new draft" (which would trigger TemplateConflictPrompt right after
+// every reset). Subsequent edits re-create a local draft naturally.
 const resetDropdownOpen = ref(false);
 function resetTo(src: 'bundled' | 'remote'): void {
   loadFrom(src);
-  if (draft.value) persistLocal(draft.value);
+  if (editName.value) localEdits.remove(editName.value);
   resetDropdownOpen.value = false;
 }
 
@@ -667,39 +689,43 @@ const isDirty = computed<boolean>(() =>
 );
 
 // Which source to seed the editor from for the current selection.
-// Priority mirrors what the operator sees on the live overview pages:
+// Remote is the canonical baseline — it's what `pickOverviewContent`
+// in the runtime bundle serves to end users — so the editor opens
+// from remote on every re-mount when remote is reachable. Priority:
 //   1. Local draft — unpublished in-progress edits in this browser.
-//   2. Remote — when bundled and remote diverged (or the row is
-//      remote-only), OAP is the source of truth and the runtime bundle
-//      loads remote via `pickOverviewContent` (bundle.ts). Loading
-//      bundled here silently disagrees with the live UI.
-//   3. Bundled — synced rows are byte-equal anyway; bundled-fallback is
-//      the bundle's only source.
-//   4. Last resort: remote when there's no bundled at all
-//      (defensive — every overview ships a bundled default today).
+//   2. Remote — the default for every re-entry when remote exists.
+//   3. Bundled — fallback for bundled-only overviews (no OAP row).
 function defaultEditorSource(): 'local' | 'bundled' | 'remote' {
   if (hasLocalDraft.value) return 'local';
-  if (editorSource.value === 'remote' && remoteAvailable.value) return 
'remote';
-  const badge = editName.value ? sync.badgeFor(editName.value) : null;
-  if ((badge === 'diverged' || badge === 'remote-only') && 
remoteAvailable.value) {
-    return 'remote';
-  }
-  if (bundledContent()) return 'bundled';
   if (remoteAvailable.value) return 'remote';
+  if (bundledContent()) return 'bundled';
   return 'bundled';
 }
 
 // Seed the editor when the selected overview (or a background refetch)
-// changes — but never clobber unsaved keystrokes.
+// changes — but never clobber unsaved keystrokes OR an operator-driven
+// load (Reset to bundled / local draft). Defer the initial seed until
+// `sourcesReady` so the editor doesn't visibly flip from bundled to
+// remote on first mount. Once sources are ready the watcher runs
+// every time the operator picks a different overview.
 watch(
-  [selectedId, () => detailQuery.data.value, hasLocalDraft, remoteAvailable],
+  [selectedId, () => detailQuery.data.value],
   () => {
+    if (!sourcesReady.value) return;
     if (isDirty.value) return;
     loadFrom(defaultEditorSource());
   },
   { immediate: true },
 );
 
+// First-mount deferred seed. The moment sources transition to ready,
+// run the seed for the currently-selected overview once.
+watch(sourcesReady, (ready, wasReady) => {
+  if (ready && !wasReady && selectedId.value && !isDirty.value) {
+    loadFrom(defaultEditorSource());
+  }
+});
+
 /** Save the editor to the browser local draft. Never writes OAP. */
 function onSave(): void {
   if (!draft.value || !editName.value) return;
@@ -1058,24 +1084,26 @@ function widgetKindLabel(type: OverviewWidget['type']): 
string {
             <!-- Source / save / publish actions, right-aligned (same row as
                  the title + tabs, mirroring the layer dashboards editor). -->
             <div class="ot__head-actions">
-              <!-- Source pill. Mirrors the layer dashboards editor wording
-                   ("from <source>") so operators get the same hint about
-                   which bytes are currently in the buffer:
-                     - local   = unpublished browser draft
-                     - remote  = OAP-live (default for diverged / remote-
-                                 only rows so the editor agrees with what
-                                 the live menu renders)
-                     - bundled = shipped default (only when the row is
-                                 bundled-fallback or the operator just
-                                 hit "Reset to bundled")
-                   Hidden when the row is "synced" (bundled === remote) and
-                   no local draft exists, because in that case "from
-                   anything" is unambiguous noise. -->
+              <!-- Source pill — three visible states gated on
+                   `sourcesReady` so the initial render doesn't flash
+                   "from bundled" while the config bundle is still
+                   resolving (see Layer Dashboards admin for the
+                   parallel fix). -->
+              <span
+                v-if="sourcesReady && editorSource === 'local'"
+                class="ot__src is-local"
+                :title="t('Unpublished local edits — Push to publish to OAP.')"
+              >{{ t('from local') }}</span>
+              <span
+                v-else-if="sourcesReady && editorSource === 'bundled'"
+                class="ot__src is-bundled"
+                :title="t('Showing the shipped bundled default — Push to 
overwrite OAP with bundled.')"
+              >{{ t('from bundled') }}</span>
               <span
-                v-if="editorSource === 'local' || !isSynced"
-                class="ot__src"
-                :title="`Editing from: ${editorSource}`"
-              >from {{ editorSource }}</span>
+                v-else-if="sourcesReady && editorSource === 'remote'"
+                class="ot__src is-remote"
+                :title="t('Showing the OAP-live version. End users render the 
same bytes.')"
+              >{{ t('from remote') }}</span>
               <div class="reset-dd">
                 <button type="button" class="ot__btn" 
@click="resetDropdownOpen = !resetDropdownOpen">
                   reset to <span class="caret" :class="{ open: 
resetDropdownOpen }">›</span>
@@ -1083,8 +1111,20 @@ function widgetKindLabel(type: OverviewWidget['type']): 
string {
                 <template v-if="resetDropdownOpen">
                   <div class="reset-dd-backdrop" @click="resetDropdownOpen = 
false" />
                   <div class="reset-dd-pop">
-                    <button v-if="!isSynced" class="reset-dd-item" 
type="button" title="Discard current edits and reload the bundled default." 
@click="resetTo('bundled')">Bundled</button>
-                    <button class="reset-dd-item" type="button" 
:disabled="!remoteAvailable" :title="remoteAvailable ? 'Discard current edits 
and reload OAP\'s live version.' : 'OAP has no copy yet.'" 
@click="resetTo('remote')">Remote</button>
+                    <!-- Bundled stays visible when synced; disabled
+                         with "(synced)" tail so the option is
+                         recognizable but the operator knows there's
+                         nothing meaningful to reset to. -->
+                    <button
+                      class="reset-dd-item"
+                      type="button"
+                      :disabled="isSynced"
+                      :title="isSynced
+                        ? t('Bundled equals OAP-live for this row — nothing to 
reset to.')
+                        : t('Discard current edits and reload the bundled 
default.')"
+                      @click="resetTo('bundled')"
+                    >{{ t('Bundled') }}<span v-if="isSynced" 
class="reset-dd-suffix"> {{ t('(synced)') }}</span></button>
+                    <button class="reset-dd-item" type="button" 
:disabled="!remoteAvailable" :title="remoteAvailable ? t('Discard current edits 
and reload OAP\'s live version.') : t('OAP has no copy yet.')" 
@click="resetTo('remote')">{{ t('Remote') }}</button>
                   </div>
                 </template>
               </div>
diff --git a/apps/ui/src/features/admin/translations/TranslationsView.vue 
b/apps/ui/src/features/admin/translations/TranslationsView.vue
index 108d64d..6cacbf6 100644
--- a/apps/ui/src/features/admin/translations/TranslationsView.vue
+++ b/apps/ui/src/features/admin/translations/TranslationsView.vue
@@ -503,6 +503,58 @@ const dirty = computed<boolean>(() => {
 const saveMsg = ref<string | null>(null);
 const saving = ref(false);
 
+/* ── Editor source tracking ────────────────────────────────────────
+ * Matches the Layer Dashboards + Overview Templates admin editors:
+ * the pill always shows one of three states (`from local` / `from
+ * bundled` / `from remote`) and the dropdown lets the operator reset
+ * to the disk-shipped overlay only ("bundled") or to whatever OAP
+ * currently has ("remote"). Local edits flip the pill to "from
+ * local"; discard flips it back to "from remote". */
+const editorSource = ref<'local' | 'bundled' | 'remote'>('remote');
+
+// Switching template or target locale recomputes the source from
+// whether the operator has unstaged local edits for that (name, loc).
+// Don't clobber an explicitly-set source (bundled) inside the same
+// locale — the watcher only fires when name/locale changes.
+watch([selectedName, target], () => {
+  editorSource.value = hasStagedLocal.value ? 'local' : 'remote';
+});
+
+/** Rebuild the draft for one (name, locale) from a specific source.
+ *  - `remote` reproduces the default layering (disk overlay first, OAP
+ *    overlay wins per leaf) — the canonical baseline the operator
+ *    sees in the live UI.
+ *  - `bundled` shows ONLY the disk-shipped overlay, ignoring OAP. Use
+ *    this to compare against the on-disk seed without OAP overrides. */
+function rebuildDraftForLocale(name: string, loc: string, src: 'remote' | 
'bundled'): void {
+  const eff = effective.value;
+  if (!eff) return;
+  const tplMap = { ...(draft.value[name] ?? {}) };
+  delete tplMap[loc];
+  draft.value = { ...draft.value, [name]: tplMap };
+  const snap = fetchedOverlays.value[overlayKey(name, loc)];
+  if (!snap) return;
+  applyOverlayToDraft(name, loc, snap.disk, eff);
+  if (src === 'remote') {
+    applyOverlayToDraft(name, loc, snap.oap, eff);
+  }
+}
+
+/** Reset to dropdown — discard the current local draft and reload the
+ *  draft from the picked source. Symmetric with the layer / overview
+ *  editors. */
+const resetDropdownOpen = ref(false);
+function resetTo(src: 'remote' | 'bundled'): void {
+  const name = selectedName.value;
+  const loc = target.value;
+  if (!name) return;
+  localEdits.remove(name, loc);
+  rebuildDraftForLocale(name, loc, src);
+  editorSource.value = src;
+  resetDropdownOpen.value = false;
+  closePanel();
+}
+
 /** Persist the current draft to localStorage for the active locale.
  *  Survives reloads, doesn't reach other users, doesn't touch OAP. */
 function stageLocal(): void {
@@ -512,6 +564,7 @@ function stageLocal(): void {
   if (!name) return;
   if (overlay === null) localEdits.remove(name, loc);
   else localEdits.set(name, loc, overlay);
+  editorSource.value = 'local';
   saveMsg.value = t('Staged {locale} locally.', { locale: 
LOCALE_NATIVE_LABEL[loc] });
   closePanel();
   setTimeout(() => (saveMsg.value = null), 4000);
@@ -589,17 +642,8 @@ function discardLocal(): void {
   const loc = target.value;
   if (!name) return;
   localEdits.remove(name, loc);
-  // Re-seed the active locale's draft from OAP + disk.
-  const tplMap = { ...(draft.value[name] ?? {}) };
-  delete tplMap[loc];
-  draft.value = { ...draft.value, [name]: tplMap };
-  // Force the snapshot to be re-applied (it was already fetched but
-  // we just dropped the draft for this locale).
-  const snap = fetchedOverlays.value[overlayKey(name, loc)];
-  if (snap && effective.value) {
-    applyOverlayToDraft(name, loc, snap.disk, effective.value);
-    applyOverlayToDraft(name, loc, snap.oap, effective.value);
-  }
+  rebuildDraftForLocale(name, loc, 'remote');
+  editorSource.value = 'remote';
   saveMsg.value = t('Discarded {locale} local draft.', { locale: 
LOCALE_NATIVE_LABEL[loc] });
   closePanel();
   setTimeout(() => (saveMsg.value = null), 3000);
@@ -698,8 +742,59 @@ function leafLabel(segments: Array<string | number>): 
string {
         />
       </label>
       <span class="tv__progress">{{ t('{filled} / {total} translated', { 
filled: filledCount, total: allFields.length }) }}</span>
+      <!-- Source pill — three visible states, one per `editorSource`,
+           matching the Layer Dashboards + Overview Templates admin
+           editors. `from remote` is the default (showing what OAP
+           currently has); after Reset to bundled the editor shows
+           only the disk-shipped overlay (`from bundled`); after any
+           local stage the pill flips to `from local`. -->
+      <span
+        v-if="editorSource === 'local'"
+        class="tv__src is-local"
+        :title="t('Unpublished local edits — Push to publish to OAP.')"
+      >{{ t('from local') }}</span>
+      <span
+        v-else-if="editorSource === 'bundled'"
+        class="tv__src is-bundled"
+        :title="t('Showing the shipped bundled default — Push to overwrite OAP 
with bundled.')"
+      >{{ t('from bundled') }}</span>
+      <span
+        v-else-if="editorSource === 'remote'"
+        class="tv__src is-remote"
+        :title="t('Showing the OAP-live version. End users render the same 
bytes.')"
+      >{{ t('from remote') }}</span>
 
       <div class="tv__actions">
+        <!-- Reset to ▾ dropdown — matches the layer / overview
+             editors. Discards local edits and re-seeds the draft from
+             the picked source. -->
+        <div class="reset-dd">
+          <button
+            type="button"
+            class="sw-btn"
+            :disabled="readOnly || !selectedName"
+            @click="resetDropdownOpen = !resetDropdownOpen"
+          >
+            {{ t('reset to') }} <span class="caret" :class="{ open: 
resetDropdownOpen }">›</span>
+          </button>
+          <template v-if="resetDropdownOpen">
+            <div class="reset-dd-backdrop" @click="resetDropdownOpen = false" 
/>
+            <div class="reset-dd-pop">
+              <button
+                class="reset-dd-item"
+                type="button"
+                :title="t('Discard local edits and reload only the 
disk-shipped overlay (ignore OAP overlay).')"
+                @click="resetTo('bundled')"
+              >{{ t('Bundled') }}</button>
+              <button
+                class="reset-dd-item"
+                type="button"
+                :title="t('Discard local edits and reload from the OAP overlay 
row.')"
+                @click="resetTo('remote')"
+              >{{ t('Remote') }}</button>
+            </div>
+          </template>
+        </div>
         <button
           v-if="hasStagedLocal"
           type="button"
@@ -825,7 +920,54 @@ function leafLabel(segments: Array<string | number>): 
string {
   padding: 3px 8px; background: var(--sw-bg-2); border-radius: 3px;
   font-variant-numeric: tabular-nums;
 }
-.tv__actions { margin-left: auto; display: inline-flex; gap: 6px; }
+/* Source pill ("from local") — same vocabulary as the layer + overview
+ * editors so operators read the same chip across all three admin pages. */
+.tv__src {
+  font-size: var(--sw-fs-xs);
+  font-weight: var(--sw-fw-semibold);
+  letter-spacing: var(--sw-ls-caps);
+  text-transform: uppercase;
+  color: var(--sw-warn);
+  padding: 3px 8px;
+  background: var(--sw-warn-soft);
+  border-radius: 3px;
+}
+.tv__actions { margin-left: auto; display: inline-flex; gap: 6px; align-items: 
center; }
+
+/* Reset-to dropdown — vocabulary matches the layer + overview admin
+ * editors so the affordance reads the same across all three pages. */
+.reset-dd { position: relative; }
+.reset-dd .caret {
+  display: inline-block; margin-left: 4px;
+  transition: transform 0.1s ease;
+}
+.reset-dd .caret.open { transform: rotate(90deg); }
+.reset-dd-backdrop {
+  position: fixed; inset: 0; z-index: 50;
+}
+.reset-dd-pop {
+  position: absolute; top: calc(100% + 4px); right: 0;
+  z-index: 51;
+  min-width: 160px;
+  background: var(--sw-bg-1);
+  border: 1px solid var(--sw-line-2);
+  border-radius: 4px;
+  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
+  padding: 4px;
+  display: flex; flex-direction: column; gap: 2px;
+}
+.reset-dd-item {
+  text-align: left;
+  padding: 6px 10px;
+  background: transparent;
+  border: 0;
+  border-radius: 3px;
+  color: var(--sw-fg-1);
+  font-size: var(--sw-fs-sm);
+  cursor: pointer;
+}
+.reset-dd-item:hover:not(:disabled) { background: var(--sw-bg-2); color: 
var(--sw-fg-0); }
+.reset-dd-item:disabled { opacity: 0.4; cursor: not-allowed; }
 .tv__msg {
   padding: 6px 10px; font-size: 12px; color: var(--sw-fg-2);
   background: var(--sw-bg-1); border: 1px solid var(--sw-line-2); 
border-radius: 4px;
diff --git a/apps/ui/src/i18n/locales/de.json b/apps/ui/src/i18n/locales/de.json
index aef836c..af41a71 100644
--- a/apps/ui/src/i18n/locales/de.json
+++ b/apps/ui/src/i18n/locales/de.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "Fenster überschreitet Obergrenze von {h} h",
   "last {window}": "letzte {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "Betrieb · OAP-Konfiguration"
+  "Operate · OAP configuration": "Betrieb · OAP-Konfiguration",
+  "from {source}": "aus {source}",
+  "local": "lokal",
+  "Local": "Lokal",
+  "Editor buffer differs from OAP — not the live version.": "Editor-Inhalt 
weicht von OAP ab — nicht die Live-Version.",
+  "from local": "aus lokal",
+  "from bundled": "aus mitgeliefert",
+  "(synced)": "(synchron)",
+  "Unpublished local edits — Push to publish to OAP.": "Unveröffentlichte 
lokale Änderungen — auf OAP per Push veröffentlichen.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"Zeigt den mitgelieferten Standard — per Push OAP mit dem mitgelieferten Stand 
überschreiben.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "Mitgeliefert 
entspricht der OAP-Live-Version — nichts zum Zurücksetzen.",
+  "Discard current edits and reload the bundled (shipped) default.": "Aktuelle 
Änderungen verwerfen und mitgelieferten Standard neu laden.",
+  "Discard current edits and reload the bundled default.": "Aktuelle 
Änderungen verwerfen und mitgelieferten Standard neu laden.",
+  "Discard current edits and reload OAP's live version.": "Aktuelle Änderungen 
verwerfen und OAP-Live-Version neu laden.",
+  "OAP has no copy of this template yet.": "OAP hat noch keine Kopie dieses 
Templates.",
+  "OAP has no copy yet.": "OAP hat noch keine Kopie.",
+  "Bundled": "Mitgeliefert",
+  "Remote": "Remote",
+  "from remote": "aus remote",
+  "Showing the OAP-live version. End users render the same bytes.": "Zeigt die 
OAP-Live-Version — exakt was Endnutzer sehen.",
+  "reset to": "zurücksetzen auf",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "Lokale Änderungen verwerfen und nur das mitgelieferte Disk-Overlay 
neu laden (OAP-Overlay ignorieren).",
+  "Discard local edits and reload from the OAP overlay row.": "Lokale 
Änderungen verwerfen und aus der OAP-Overlay-Zeile neu laden."
 }
diff --git a/apps/ui/src/i18n/locales/en.json b/apps/ui/src/i18n/locales/en.json
index 3cc5141..7f478ce 100644
--- a/apps/ui/src/i18n/locales/en.json
+++ b/apps/ui/src/i18n/locales/en.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "Window exceeds {h}h cap",
   "last {window}": "last {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "Operate · OAP configuration"
+  "Operate · OAP configuration": "Operate · OAP configuration",
+  "from {source}": "from {source}",
+  "local": "local",
+  "Local": "Local",
+  "Editor buffer differs from OAP — not the live version.": "Editor buffer 
differs from OAP — not the live version.",
+  "from local": "from local",
+  "from bundled": "from bundled",
+  "(synced)": "(synced)",
+  "Unpublished local edits — Push to publish to OAP.": "Unpublished local 
edits — Push to publish to OAP.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"Showing the shipped bundled default — Push to overwrite OAP with bundled.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "Bundled 
equals OAP-live for this row — nothing to reset to.",
+  "Discard current edits and reload the bundled (shipped) default.": "Discard 
current edits and reload the bundled (shipped) default.",
+  "Discard current edits and reload the bundled default.": "Discard current 
edits and reload the bundled default.",
+  "Discard current edits and reload OAP's live version.": "Discard current 
edits and reload OAP's live version.",
+  "OAP has no copy of this template yet.": "OAP has no copy of this template 
yet.",
+  "OAP has no copy yet.": "OAP has no copy yet.",
+  "Bundled": "Bundled",
+  "Remote": "Remote",
+  "from remote": "from remote",
+  "Showing the OAP-live version. End users render the same bytes.": "Showing 
the OAP-live version. End users render the same bytes.",
+  "reset to": "reset to",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "Discard local edits and reload only the disk-shipped overlay 
(ignore OAP overlay).",
+  "Discard local edits and reload from the OAP overlay row.": "Discard local 
edits and reload from the OAP overlay row."
 }
diff --git a/apps/ui/src/i18n/locales/es.json b/apps/ui/src/i18n/locales/es.json
index 7f9a23d..45bad9b 100644
--- a/apps/ui/src/i18n/locales/es.json
+++ b/apps/ui/src/i18n/locales/es.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "La ventana supera el tope de {h}h",
   "last {window}": "últimos {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "Operar · Configuración de OAP"
+  "Operate · OAP configuration": "Operar · Configuración de OAP",
+  "from {source}": "desde {source}",
+  "local": "local",
+  "Local": "Local",
+  "Editor buffer differs from OAP — not the live version.": "El buffer del 
editor difiere de OAP — no es la versión en vivo.",
+  "from local": "desde local",
+  "from bundled": "desde bundled",
+  "(synced)": "(sincronizado)",
+  "Unpublished local edits — Push to publish to OAP.": "Cambios locales sin 
publicar — usa Push para publicar en OAP.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"Mostrando el valor predeterminado bundled — Push sobrescribe OAP con bundled.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "En esta 
fila, bundled coincide con OAP-live — no hay nada que restablecer.",
+  "Discard current edits and reload the bundled (shipped) default.": "Descarta 
los cambios actuales y recarga el valor predeterminado bundled (de fábrica).",
+  "Discard current edits and reload the bundled default.": "Descarta los 
cambios actuales y recarga el valor predeterminado bundled.",
+  "Discard current edits and reload OAP's live version.": "Descarta los 
cambios actuales y recarga la versión en vivo de OAP.",
+  "OAP has no copy of this template yet.": "OAP aún no tiene una copia de esta 
plantilla.",
+  "OAP has no copy yet.": "OAP aún no tiene una copia.",
+  "Bundled": "Bundled",
+  "Remote": "Remote",
+  "from remote": "desde remote",
+  "Showing the OAP-live version. End users render the same bytes.": "Mostrando 
la versión en vivo de OAP — los usuarios finales ven los mismos bytes.",
+  "reset to": "restablecer a",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "Descarta los cambios locales y recarga solo el overlay de disco 
(ignora el overlay de OAP).",
+  "Discard local edits and reload from the OAP overlay row.": "Descarta los 
cambios locales y recarga desde la fila de overlay de OAP."
 }
diff --git a/apps/ui/src/i18n/locales/fr.json b/apps/ui/src/i18n/locales/fr.json
index 5471b28..cd03348 100644
--- a/apps/ui/src/i18n/locales/fr.json
+++ b/apps/ui/src/i18n/locales/fr.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "La fenêtre dépasse le plafond de {h} h",
   "last {window}": "dernier(s) {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "Exploitation · Configuration OAP"
+  "Operate · OAP configuration": "Exploitation · Configuration OAP",
+  "from {source}": "depuis {source}",
+  "local": "local",
+  "Local": "Local",
+  "Editor buffer differs from OAP — not the live version.": "Le contenu de 
l'éditeur diffère d'OAP — ce n'est pas la version en ligne.",
+  "from local": "depuis local",
+  "from bundled": "depuis bundled",
+  "(synced)": "(synchronisé)",
+  "Unpublished local edits — Push to publish to OAP.": "Modifications locales 
non publiées — Push pour publier sur OAP.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"Affiche la valeur par défaut bundled livrée — Push pour écraser OAP avec 
bundled.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "Pour cette 
ligne, bundled est identique à la version OAP en ligne — rien à réinitialiser.",
+  "Discard current edits and reload the bundled (shipped) default.": 
"Abandonner les modifications actuelles et recharger la valeur par défaut 
bundled (livrée).",
+  "Discard current edits and reload the bundled default.": "Abandonner les 
modifications actuelles et recharger la valeur par défaut bundled.",
+  "Discard current edits and reload OAP's live version.": "Abandonner les 
modifications actuelles et recharger la version OAP en ligne.",
+  "OAP has no copy of this template yet.": "OAP n'a pas encore de copie de ce 
template.",
+  "OAP has no copy yet.": "OAP n'a pas encore de copie.",
+  "Bundled": "Bundled",
+  "Remote": "Remote",
+  "from remote": "depuis remote",
+  "Showing the OAP-live version. End users render the same bytes.": "Affiche 
la version OAP en ligne — exactement ce que voient les utilisateurs finaux.",
+  "reset to": "réinitialiser à",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "Abandonner les modifications locales et recharger uniquement 
l'overlay livré sur disque (ignorer l'overlay OAP).",
+  "Discard local edits and reload from the OAP overlay row.": "Abandonner les 
modifications locales et recharger depuis la ligne d'overlay OAP."
 }
diff --git a/apps/ui/src/i18n/locales/ja.json b/apps/ui/src/i18n/locales/ja.json
index 0864c8a..d5aefd9 100644
--- a/apps/ui/src/i18n/locales/ja.json
+++ b/apps/ui/src/i18n/locales/ja.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "範囲が {h} 時間の上限を超えています",
   "last {window}": "直近 {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "運用 · OAP 設定"
+  "Operate · OAP configuration": "運用 · OAP 設定",
+  "from {source}": "{source} から",
+  "local": "ローカル",
+  "Local": "ローカル",
+  "Editor buffer differs from OAP — not the live version.": "エディタの内容は OAP 
と異なります — ライブ版ではありません。",
+  "from local": "ローカルから",
+  "from bundled": "バンドルから",
+  "(synced)": "(同期済み)",
+  "Unpublished local edits — Push to publish to OAP.": "未公開のローカル編集 — Push で 
OAP に公開します。",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"同梱されたバンドルのデフォルトを表示中 — Push でバンドルを OAP に上書きします。",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "この行はバンドルと 
OAP のライブが一致しています — リセット先がありません。",
+  "Discard current edits and reload the bundled (shipped) default.": 
"現在の編集を破棄し、同梱されたバンドルのデフォルトを再読み込みします。",
+  "Discard current edits and reload the bundled default.": 
"現在の編集を破棄し、バンドルのデフォルトを再読み込みします。",
+  "Discard current edits and reload OAP's live version.": "現在の編集を破棄し、OAP 
のライブ版を再読み込みします。",
+  "OAP has no copy of this template yet.": "OAP にはまだこのテンプレートのコピーがありません。",
+  "OAP has no copy yet.": "OAP にはまだコピーがありません。",
+  "Bundled": "バンドル",
+  "Remote": "リモート",
+  "from remote": "リモートから",
+  "Showing the OAP-live version. End users render the same bytes.": "OAP 
のライブ版を表示中 — エンドユーザーが見るものと同じです。",
+  "reset to": "リセット先",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "ローカル編集を破棄し、同梱のディスクオーバーレイのみを再読み込み(OAP オーバーレイは無視)。",
+  "Discard local edits and reload from the OAP overlay row.": "ローカル編集を破棄し、OAP 
オーバーレイ行から再読み込み。"
 }
diff --git a/apps/ui/src/i18n/locales/ko.json b/apps/ui/src/i18n/locales/ko.json
index 3d08e58..6f7613b 100644
--- a/apps/ui/src/i18n/locales/ko.json
+++ b/apps/ui/src/i18n/locales/ko.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "범위가 {h}시간 상한을 초과합니다",
   "last {window}": "최근 {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "운영 · OAP 구성"
+  "Operate · OAP configuration": "운영 · OAP 구성",
+  "from {source}": "{source}에서",
+  "local": "로컬",
+  "Local": "로컬",
+  "Editor buffer differs from OAP — not the live version.": "에디터 내용이 OAP와 다릅니다 
— 라이브 버전이 아닙니다.",
+  "from local": "로컬에서",
+  "from bundled": "번들에서",
+  "(synced)": "(동기화됨)",
+  "Unpublished local edits — Push to publish to OAP.": "게시되지 않은 로컬 편집 — Push로 
OAP에 게시합니다.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"번들된 기본값을 표시 중 — Push로 번들을 OAP에 덮어씁니다.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "이 행은 번들과 OAP 
라이브가 동일 — 재설정할 대상이 없습니다.",
+  "Discard current edits and reload the bundled (shipped) default.": "현재 편집을 
버리고 번들(배포) 기본값을 다시 불러옵니다.",
+  "Discard current edits and reload the bundled default.": "현재 편집을 버리고 번들 기본값을 
다시 불러옵니다.",
+  "Discard current edits and reload OAP's live version.": "현재 편집을 버리고 OAP 라이브 
버전을 다시 불러옵니다.",
+  "OAP has no copy of this template yet.": "OAP에 아직 이 템플릿 복사본이 없습니다.",
+  "OAP has no copy yet.": "OAP에 아직 복사본이 없습니다.",
+  "Bundled": "번들",
+  "Remote": "원격",
+  "from remote": "원격에서",
+  "Showing the OAP-live version. End users render the same bytes.": "OAP 라이브 
버전 표시 중 — 최종 사용자가 보는 것과 동일합니다.",
+  "reset to": "재설정 대상",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "로컬 편집을 버리고 디스크에 동봉된 오버레이만 다시 불러옵니다(OAP 오버레이 무시).",
+  "Discard local edits and reload from the OAP overlay row.": "로컬 편집을 버리고 OAP 
오버레이 행에서 다시 불러옵니다."
 }
diff --git a/apps/ui/src/i18n/locales/pt.json b/apps/ui/src/i18n/locales/pt.json
index 52a347a..e12ea9a 100644
--- a/apps/ui/src/i18n/locales/pt.json
+++ b/apps/ui/src/i18n/locales/pt.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "A janela excede o limite de {h}h",
   "last {window}": "últimos {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "Operar · Configuração do OAP"
+  "Operate · OAP configuration": "Operar · Configuração do OAP",
+  "from {source}": "a partir de {source}",
+  "local": "local",
+  "Local": "Local",
+  "Editor buffer differs from OAP — not the live version.": "O conteúdo do 
editor difere do OAP — não é a versão em produção.",
+  "from local": "a partir de local",
+  "from bundled": "a partir de bundled",
+  "(synced)": "(sincronizado)",
+  "Unpublished local edits — Push to publish to OAP.": "Edições locais não 
publicadas — use Push para publicar no OAP.",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"Exibindo o padrão bundled — Push sobrescreve o OAP com bundled.",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "Nesta linha, 
bundled coincide com a versão em produção do OAP — nada para redefinir.",
+  "Discard current edits and reload the bundled (shipped) default.": 
"Descartar edições atuais e recarregar o padrão bundled (de fábrica).",
+  "Discard current edits and reload the bundled default.": "Descartar edições 
atuais e recarregar o padrão bundled.",
+  "Discard current edits and reload OAP's live version.": "Descartar edições 
atuais e recarregar a versão em produção do OAP.",
+  "OAP has no copy of this template yet.": "OAP ainda não tem uma cópia deste 
template.",
+  "OAP has no copy yet.": "OAP ainda não tem uma cópia.",
+  "Bundled": "Bundled",
+  "Remote": "Remote",
+  "from remote": "a partir de remote",
+  "Showing the OAP-live version. End users render the same bytes.": "Exibindo 
a versão em produção do OAP — os usuários finais veem exatamente isto.",
+  "reset to": "redefinir para",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "Descartar edições locais e recarregar apenas o overlay do disco 
(ignorar o overlay do OAP).",
+  "Discard local edits and reload from the OAP overlay row.": "Descartar 
edições locais e recarregar a partir da linha de overlay do OAP."
 }
diff --git a/apps/ui/src/i18n/locales/zh-CN.json 
b/apps/ui/src/i18n/locales/zh-CN.json
index 86ce28c..bc44031 100644
--- a/apps/ui/src/i18n/locales/zh-CN.json
+++ b/apps/ui/src/i18n/locales/zh-CN.json
@@ -1157,5 +1157,27 @@
   "Window exceeds {h}h cap": "窗口超过 {h} 小时上限",
   "last {window}": "最近 {window}",
   "Metrics Analysis Language - …": "Metrics Analysis Language - …",
-  "Operate · OAP configuration": "运维 · OAP 配置"
+  "Operate · OAP configuration": "运维 · OAP 配置",
+  "from {source}": "来自 {source}",
+  "local": "本地",
+  "Local": "本地",
+  "Editor buffer differs from OAP — not the live version.": "编辑器内容与 OAP 不同 — 
不是线上版本。",
+  "from local": "来自本地",
+  "from bundled": "来自内置",
+  "(synced)": "(已同步)",
+  "Unpublished local edits — Push to publish to OAP.": "未发布的本地编辑 — 推送以发布到 
OAP。",
+  "Showing the shipped bundled default — Push to overwrite OAP with bundled.": 
"当前显示内置默认值 — 推送会用内置版覆盖 OAP。",
+  "Bundled equals OAP-live for this row — nothing to reset to.": "该行的内置版与 OAP 
实时版本一致 — 无需重置。",
+  "Discard current edits and reload the bundled (shipped) default.": 
"丢弃当前编辑,重新加载内置(发行)默认值。",
+  "Discard current edits and reload the bundled default.": "丢弃当前编辑,重新加载内置默认值。",
+  "Discard current edits and reload OAP's live version.": "丢弃当前编辑,重新加载 OAP 
实时版本。",
+  "OAP has no copy of this template yet.": "OAP 还没有此模板的副本。",
+  "OAP has no copy yet.": "OAP 还没有副本。",
+  "Bundled": "内置",
+  "Remote": "远端",
+  "from remote": "来自远端",
+  "Showing the OAP-live version. End users render the same bytes.": "显示 OAP 
实时版本 — 与终端用户看到的内容一致。",
+  "reset to": "重置为",
+  "Discard local edits and reload only the disk-shipped overlay (ignore OAP 
overlay).": "丢弃本地编辑,只重新加载随包的磁盘覆盖层(忽略 OAP 覆盖层)。",
+  "Discard local edits and reload from the OAP overlay row.": "丢弃本地编辑,从 OAP 
覆盖行重新加载。"
 }

Reply via email to