// Recursive translation tree.
// Use an interface to avoid TS2456 (circular type alias) during d.ts generation.
export interface Translations {
  [key: string]: string | Translations;
}

export interface I18nInit {
  /** Active locale, e.g. 'fr' */
  locale?: string;
  /** Fallback locale used when a key is missing in the active locale. Defaults to 'en'. */
  fallbackLocale?: string;
  /** Map of locale -> translations */
  translations?: Record<string, Translations>;
  /** If true, log a console warning once per missing key (in non-prod). */
  warnOnMissing?: boolean;
}

export interface I18nApi {
  t: (key: string, defaultValueOrParams?: string | Record<string, string | number>, params?: Record<string, string | number>) => string;
  getLocale: () => string;
  setLocale: (locale: string) => void;
  getFallbackLocale: () => string;
  setFallbackLocale: (locale: string) => void;
  addTranslations: (locale: string, bundle: Translations) => void;
  getTranslations: () => Record<string, Translations>;
}

function isObject(v: unknown): v is Record<string, any> {
  return !!v && typeof v === 'object' && !Array.isArray(v);
}

function deepMerge<T extends Record<string, any>>(base: T, patch: any): T {
  if (!isObject(patch)) return base;
  const out: any = { ...base };
  Object.keys(patch).forEach((k) => {
    const pv = patch[k];
    const bv = (out as any)[k];
    if (isObject(bv) && isObject(pv)) {
      (out as any)[k] = deepMerge(bv, pv);
    } else {
      (out as any)[k] = pv;
    }
  });
  return out;
}

function getPath(bundle: Translations | undefined, key: string): string | undefined {
  if (!bundle) return undefined;

  // Support both nested objects (ui.foo.bar) AND flat keys ('ui.foo.bar').
  const direct = (bundle as any)[key];
  if (typeof direct === 'string') return direct;

  // Some bundles store dotted keys inside a namespace object (e.g. bundle.ui['ui.xxx']).
  // If so, try to resolve it before traversing parts.
  const firstDot = key.indexOf('.');
  if (firstDot > 0) {
    const ns = key.slice(0, firstDot);
    const scoped = (bundle as any)[ns];
    if (isObject(scoped)) {
      const scopedDirect = (scoped as any)[key];
      if (typeof scopedDirect === 'string') return scopedDirect;
    }
  }

  const parts = key.split('.').filter(Boolean);
  let cur: any = bundle;
  for (const p of parts) {
    if (typeof cur === 'string') return undefined;
    if (!isObject(cur)) return undefined;
    cur = (cur as any)[p];
    if (cur === undefined) return undefined;
  }
  return typeof cur === 'string' ? cur : undefined;
}

function interpolate(template: string, params?: Record<string, string | number>): string {
  if (!params) return template;
  return template.replace(/\{(.*?)\}/g, (_, rawKey) => {
    const k = String(rawKey).trim();
    const v = (params as any)[k];
    return v === undefined || v === null ? `{${k}}` : String(v);
  });
}

/**
 * Built-in English strings for core UI.
 * Keep this small; modules can add their own keys.
 */
export const EN_TRANSLATIONS: Translations = {

  modules: {

    pricingRules: 'Pricing Rules',
    state: 'State',
    tools: 'Tools',
    history: 'History',
    download: 'Download',
    views: 'Views',
    designs: 'Designs',
    text: 'Text',
    image: 'Images',
    images: 'Images',
    shapes: 'Shapes',
    layers: 'Layers',
    zoom: 'Zoom',
    magnifier: 'Magnifier',
    ruler: 'Ruler',
    colors: 'Colors',
    studio: 'Studio',
    transform: 'Transform',
    camera: 'Camera',
    pricing: 'Pricing',
    elements: 'Elements',
    drawingAreas: 'Drawing Areas',
    fixedSlots: 'Fixed Slots',
    license: 'License',
    modelOptimizer: 'GLB Optimizer',
    'font-family': 'Font Family',
    'text-formatting': 'Text Formatting',
    'text-curve': 'Text Curve',
    'upload-image': 'Upload Image',
    'pixabay-image': 'Pixabay',
    'pexels-image': 'Pexels',
    'facebook-image': 'Facebook',
    'qrcode-image': 'QR Code',
    test: 'Test',
  },
  menu: {
    views: 'Views',
    designs: 'Designs',
    text: 'Text',
    images: 'Images',
    shapes: 'Shapes',
    layers: 'Layers',
    zoom: 'Zoom',
    magnifier: 'Magnifier',
    ruler: 'Ruler',
    download: 'Download',
    colors: 'Colors',
    studio: 'Studio',
    transform: 'Transform',
    camera: 'Camera',
    history: 'History',
    state: 'State',
    pricing: 'Pricing',
    pricingRules: 'Pricing Rules',
    elements: 'Elements',
    drawingAreas: 'Drawing Areas',
    fixedSlots: 'Fixed Slots',
    license: 'License',
    modelOptimizer: 'GLB Optimizer',
    'upload-image': 'Upload Image',
    'pixabay-image': 'Pixabay',
    'pexels-image': 'Pexels',
    'facebook-image': 'Facebook',
    'qrcode-image': 'QR Code',
    'font-family': 'Font Family',
    'text-formatting': 'Text Formatting',
    'text-curve': 'Text Curve',
    test: 'Test',
  },
  common: {
    add: 'Add',
    apply: 'Apply',
    cancel: 'Cancel',
    close: 'Close',
    delete: 'Delete',
    edit: 'Edit',
    open: 'Open',
    remove: 'Remove',
    save: 'Save',
    search: 'Search',
    back: 'Back',
    copy: 'Copy',
    import: 'Import',
    export: 'Export',
    reset: 'Reset',
    none: 'None',
    total: 'total',
    refresh: 'Refresh',
    label: 'Label',
    color: 'Color',
    copied: 'Copied',
    loading: 'Loading…',
    loadingMore: 'Loading more…',
    loadMore: 'Load more',
    scrollToLoadMore: 'Scroll to load more',
    end: 'End',
  },

  zoom: {
    canvasZoom: 'Canvas zoom',
    adjustViewportHint: 'Adjust the viewport quickly and precisely.',
    minus10: '−10%',
    plus10: '+10%',
    zoomOut: 'Zoom out',
    zoomIn: 'Zoom in',
    resetView: 'Reset view',
    fit: 'Fit',
    fitToViewport: 'Fit to viewport',
    zoomLevel: 'Zoom level',
  },
  transform: {
    x: 'X',
    y: 'Y',
    left: 'Left',
    right: 'Right',
    up: 'Up',
    down: 'Down',
    rotation: 'Rotation',
  },
  fontFamily: {
    title: 'Font family',
  },
  text: {
    createText: 'Create text',
    clickPlaceholderToCreate: 'Click a text placeholder on the canvas to create text here.',
    typeYourText: 'Type your text...',
  },
  colors: {
    selectSubPathToEdit: 'Select a sub-path to edit.',
  },
  drawingAreas: {
    focus: 'Focus',
    description: 'Create structural zones, start drawing mode, or import an SVG-based area.',
    newAreaSetup: 'New area setup',
    idPrefix: 'ID prefix',
    idPrefixHint: 'New drawing areas will use this prefix for generated ids.',
    idPrefixPlaceholder: 'zone',
    quickShapes: 'Quick shapes',
    rectArea: 'Rect area',
    ellipseArea: 'Ellipse area',
    triangleArea: 'Triangle area',
    starArea: 'Star area',
    drawOnCanvas: 'Draw on canvas',
    drawingModeActive: 'Drawing mode active',
    drawRectArea: 'Draw rect area',
    cancelDrawing: 'Cancel drawing',
    drawEllipseArea: 'Draw ellipse area',
    drawTriangleArea: 'Draw triangle area',
    drawStarArea: 'Draw star area',
    draftHint: 'A new drawing area starts as a visible draft. Move and resize it freely, then click elsewhere to convert it into a structural drawing area.',
    svgArea: 'SVG area',
    uploadSvgArea: 'Upload SVG area',
    svgSourceHint: 'Use an SVG file or paste markup below.',
    pasteSvgMarkup: 'Paste SVG markup',
    pasteSvgMarkupHint: 'Create a draft area from pasted markup, then reposition it on the canvas.',
    svgMarkupPlaceholder: '<svg ...>...</svg>',
    createSvgDraftArea: 'Create SVG draft area',
    existingAreas: 'Existing areas',
    maxObjectsFifoReplace: 'Max objects (FIFO replace)',
  },
  fixedSlots: {
    marker: 'Marker',
    description: 'Place “fixed” markers on the canvas. Clicking a marker opens the linked module (text / image / design).',
    noMarkersYet: 'No markers yet.',
    editMarker: 'Edit marker',
    addMarker: 'Add marker',
    type: 'Type',
    optionalLabel: 'Optional label',
    lockX: 'Lock X',
    lockY: 'Lock Y',
    noRotation: 'No rotation',
    minWidth: 'Min width',
    maxWidth: 'Max width',
    minHeight: 'Min height',
    maxHeight: 'Max height',
    allowedColors: 'Allowed colors',
    fromPrintingMethods: 'From printing methods',
    csvColorsHint: 'Comma-separated values (#fff, rgb(...), etc.)',
    colorsPlaceholder: '#000000, #ffffff',
    minChars: 'Min chars',
    maxChars: 'Max chars',
    maxLines: 'Max lines',
    allowedFonts: 'Allowed fonts',
    fontFamiliesHint: 'Comma-separated font families',
    fontsPlaceholder: 'Inter, Roboto, Arial',
  },
  elements: {
    variations: 'Variations',
    instance: 'instance',
    instances: 'instances',
    copy: {
      title: 'Copy from another view',
      helper: 'Reuse an existing element definition and its placement as a starting point.',
      sourceView: 'Source view',
      sourceElement: 'Source element',
      success: 'Placement copied',
    },
  },
  studio: {
    blur: 'Blur',
    preset: 'Preset',
    presetWarm: 'Warm',
    presetCool: 'Cool',
    presetCinematic: 'Cinematic',
    presetFade: 'Fade',
    presetClarity: 'Clarity',
    presetMonoHard: 'Mono hard',
    intensity: 'Intensity',
    kernel: 'Kernel',
    kernelSharpenSoft: 'Sharpen soft',
    kernelSharpenHard: 'Sharpen hard',
    kernelEdgeDetect: 'Edge detect',
    kernelSobelX: 'Sobel X',
    kernelSobelY: 'Sobel Y',
    kernelEmboss: 'Emboss',
    svgPartsHintPrefix: 'SVG',
    svgPartsHintBody: 'use',
    svgParts: 'SVG parts',
    svgPartsHintSuffix: 'to ungroup/regroup and edit parts.',
    strength: 'Strength',
    tolerance: 'Tolerance',
    opacity: 'Opacity',
    angle: 'Angle',
    fusionMode: 'Fusion mode',
    blendNormal: 'Normal',
    blendMultiply: 'Multiply',
    blendScreen: 'Screen',
    blendOverlay: 'Overlay',
    blendDarken: 'Darken',
    blendLighten: 'Lighten',
    blendDifference: 'Difference',
  },

  pricing: {
    liveUpdatesEnabled: 'Live updates enabled.',
    loadingDesigner: 'Loading designer…',
  },
  pricingRules: {
    importPlaceholder: 'Paste a canonical pricing state JSON exported by the Pricing module.',
  },
  history: {
    empty: 'No history yet.',
    hint: 'Make changes on the canvas to create history snapshots.',
    actions: 'Actions',
    snapshots: 'Snapshots',
    stepOf: 'Step {step} of {total}',
    snapshotN: 'Snapshot {n}',
  },
  state: {
    ready: 'Ready',
    designerNotReady: 'Designer not ready',
    scopePreset: 'Scope preset',
    storageKey: 'Storage key',
    scope: {
      layout: 'Layout', modules: 'Modules', views: 'Views', drawingAreas: 'Drawing areas', objects: 'Objects', snapshots: 'Snapshots', selection: 'Selection', viewport: 'Viewport', elements: 'Elements', designs: 'Designs', colors: 'Colors', printingMethods: 'Printing methods', defaults: 'Defaults', theme: 'Theme', permissions: 'Permissions', history: 'History',
    },
    scopePresets: {
      full: 'Full (UI + Views + Canvas)',
      template: 'Template (UI + document + catalogs)',
      theme: 'Theme only',
      ui: 'UI only (layout + modules)',
      canvas: 'Canvas only (views + snapshots)',
      document: 'Document only (2D + 3D)',
    },
    actions: {
    },
    status: {
      exported: 'Exported.',
      copied: 'Copied to clipboard.',
      saved: 'Saved to storage.',
      loaded: 'Loaded from storage.',
      cleared: 'Storage cleared.',
      imported: 'Imported.',
    },
    errors: {
      apiNotAvailable: 'State API not available.',
      exportFailed: 'Export failed.',
      copyFailed: 'Copy failed (clipboard not available).',
      saveFailed: 'Save failed.',
      noStateInStorage: 'No state found in storage.',
      loadFailed: 'Load failed.',
      clearFailed: 'Clear failed.',
      importFailed: 'Import failed (invalid JSON?).',
      noPermission: "You don't have permission to use state tools.",
    },
    modals: {
      exportTitle: 'Exported state',
      importTitle: 'Import state (JSON)',
    },
    placeholders: {
      json: '{ ... }',
    },
  },
  ui: {
    enabled: 'Enabled',
    back: 'Back',
    search_photos: 'Search photos…',
    provide_it_at_init_via: 'Provide it at init via',
    text_or_url: 'Text / URL',
    facebook_access_token: 'Access token',
    add_view: 'Add view',
    bold: 'Bold',
    line_height: 'Line height',
    font: 'Font',
    plus_add_rule: '+ Add rule',
    module_not_found: 'Module not found:',
    pixabay_api_key_path: 'integrations.pixabay.apiKey',
    preset: 'Preset',
    width: 'Width',
    collapse_panel: 'Collapse panel',
    height: 'Height',
    name: 'Name',
    unit: 'Unit',
    expand_panel: 'Expand panel',
    dpi: 'DPI',
    italic: 'Italic',
    rules: 'Rules',
    module: 'Module',
    views_let_you_manage_multiple_canvases_like_front_or_back_variants_or_export_sizes: 'Views let you manage multiple canvases like front/back, variants, or export sizes.',
    count_total: '{count} total',
    create_your_first_view_to_start_designing: 'Create your first view to start designing.',
    active: 'Active',
    manage_and_switch_between_canvas_views: 'Manage and switch between canvas views.',
    advanced_mode_lets_you_edit_the_raw_json_rule_object_use_carefully: 'Advanced mode lets you edit the raw JSON rule object. Use carefully.',
    canvas_is_not_ready_yet_try_again_once_the_editor_is_fully_loaded: 'Canvas is not ready yet. Try again once the editor is fully loaded.',
    noViewModuleTitle: 'No view available',
    noViewModuleMessage: 'Create a view before using this tool.',
    noViewModuleTooltip: 'No view available. Create a view before using this tool.',
    palette: 'Palette',
    enable_curved_text_to_access_curve_settings: 'Enable curved text to access curve settings.',
    threshold: 'Threshold',
    facebook_integration_needs_an: 'Facebook integration needs an',
    export_or_import: 'Export / import',
    transform: 'Transform',
    you_dont_have_access_to_pricing: 'You don’t have access to pricing.',
    click: 'Click',
    preview: 'Preview',
    op: 'Op',
    margin_cells: 'Margin (cells)',
    export_rules_only: 'Export rules only',
    pixabay: 'Pixabay',
    pexels: 'Pexels',
    disconnect: 'Disconnect',
    foreground: 'Foreground',
    switch_view: 'Switch view',
    connect: 'Connect',
    size_px: 'Size (px)',
    permission_and_app_review: 'permission and App Review.',
    drop_here_to_dock_right: 'Drop here to dock right',
    drag_this_panel_to_move_it: 'Drag this panel to move it',
    search_pexels: 'Search Pexels…',
    serialize_pricing_rules_and_optionally_the_full_designer_config: 'Serialize pricing rules (and optionally the full designer config).',
    uploaded_images: 'Uploaded images',
    no_results_try_another_query: 'No results. Try another query.',
    select_a_fabric_text_object_text_or_i_text_or_textbox_to_edit_formatting: 'Select a Fabric text object (text / i-text / textbox) to edit formatting.',
    add_price: 'Add price',
    connect_account: 'Connect account',
    search_depositphotos: 'Search Depositphotos…',
    scope: 'Scope',
    photos: 'Photos',
    text_color: 'Text color',
    uppercase: 'Uppercase',
    value: 'Value',
    no_rules_yet_click_plus_add_rule: 'No rules yet. Click “+ Add rule”.',
    size_or_line_or_spacing: 'Size / Line / Spacing',
    create_enable_or_disable_duplicate_reorder_and_edit_rules: 'Create, enable/disable, duplicate, reorder and edit rules.',
    no_colors_available_for_the_current_printing_method: 'No colors available for the current printing method.',
    size: 'Size',
    plus_condition: '+ Condition',
    e_g_2_50: 'e.g. 2.50',
    letter_spacing: 'Letter spacing',
    disabled: '(disabled)',
    e_g_large_design_surcharge: 'e.g. Large design surcharge',
    this_uses_facebook_login_plus_graph_api: 'This uses Facebook Login + Graph API.',
    selection_menu: 'Selection menu',
    generated_locally_offline_as_svg_for_crisp_scaling: 'Generated locally (offline) as SVG for crisp scaling.',
    coming_soon: 'Coming soon',
    app_id: 'App ID',
    search_now: 'Search now',
    disabled_rules_are_ignored: 'Disabled rules are ignored.',
    pricing_rules: 'Pricing rules',
    underline: 'Underline',
    export_full_config: 'Export full config',
    you_don_and_apos_t_have_permission_to_use_state_tools: 'You don&apos;t have permission to use state tools.',
    uploaded_photos: 'Uploaded photos',
    upload: 'Upload',
    exported_state: 'Exported state',
    error_correction: 'Error correction',
    curved_text: 'Curved text',
    zoom_100_percent: 'Zoom: 100%',
    coming_soon_this_tab_will_provide_a_depositphotos_search_with_licensing_workflow: 'Coming soon. This tab will provide a Depositphotos search with licensing workflow.',
    missing_pexels_api_key_pass_it_at_init: 'Missing Pexels API key. Pass it at init:',
    total: 'Total',
    base_plus_standard_plus_rules_total: 'Base + Standard + Rules = Total',
    note_pexels_requires_attribution_in_many_use_cases_make_sure_your_product_complies_with_their_api_terms: 'Note: Pexels requires attribution in many use cases. Make sure your product complies with their API terms.',
    dock_right: 'Dock right',
    search_designs: 'Search designs…',
    plus_text: '+ Text',
    text: 'Text',
    qr_code: 'QR Code',
    no_uploads_yet: 'No uploads yet',
    add_it_in_your_designer_options_under: '. Add it in your designer options under',
    no_views_yet: 'No views yet.',
    rule_name: 'Rule name',
    background: 'Background',
    user_photos: 'user_photos',
    rule: 'Rule',
    rule_not_found: 'Rule not found.',
    base_price: 'Base price',
    style: 'Style',
    scroll_to_the_bottom_to_load_more_click_an_image_to_add_it_to_the_canvas: 'Scroll to the bottom to load more. Click an image to add it to the canvas.',
    add_rules_then_click_one_to_edit_in_a_modal: 'Add rules, then click one to edit in a modal.',
    no_sub_paths_detected_for_the_selected_group: 'No sub-paths detected for the selected group.',
    when: 'When…',
    remove_condition: 'Remove condition',
    accessing_user_photos_usually_requires_the: 'Accessing user photos usually requires the',
    no_conditions_yet_add_one: 'No conditions yet. Add one.',
    project_v1_figma_like_editor: 'Project v1 • Figma-like editor',
    no_objects_on_canvas_yet: 'No objects on canvas yet.',
    paste_json_here_accepts_either: 'Paste JSON here. Accepts either {',
    plus_rect: '+ Rect',
    app_id_read_only_here: 'App ID (read-only here)',
    asuka_designer: 'Asuka Designer',
    add_to_canvas: 'Add to canvas',
    missing_pixabay_api_key: 'Missing Pixabay API key',
    no_designs_provide_them_at_init_via: 'No designs. Provide them at init via',
    curve_type: 'Curve type',
    target_views: 'Target views',
    align: 'Align',
    import_state_json: 'Import state (JSON)',
    apply_json: 'Apply JSON',
    printing_method: 'Printing method',
    then_conditions: 'Then… (conditions)',
    facebook: 'Facebook',
    depositphotos: 'Depositphotos',
    dock_left: 'Dock left',
    edit_text: 'Edit text…',
    lowercase: 'Lowercase',
    bend: 'Bend',
    designs_items_path: 'moduleConfig.designs.items',
    move_down: 'Move down',
    facebook_app_id_path: 'integrations.facebook.appId',
    ok: 'OK',
    ad: 'AD',
    conditions_logic: 'Conditions logic',
    scopes_read_only_here: 'Scopes (read-only here)',
    target_types: 'Target types',
    svg_parts: 'SVG parts',
    palette_follows_the_active_printing_method_same_source_as_colors_module: 'Palette follows the active printing method (same source as Colors module).',
    standard: 'Standard',
    duplicate: 'Duplicate',
    apply_on: 'Apply on',
    drop_here_to_dock_left: 'Drop here to dock left',
    enter_a_value_to_preview: 'Enter a value to preview.',
    source: 'Source',
    price_breakdown: 'Price breakdown',
    sign_in_with_facebook_to_browse_photos_albums: 'to sign in with Facebook and browse your photos/albums.',
    move_up: 'Move up',
    results_update_automatically_while_you_type_debounced: 'Results update automatically while you type (debounced).',
    all: 'All',

    'download.title': 'Download',
    'download.export': 'Export',
    'download.currentView': 'Current view',
    'download.target': 'Target',
    'download.chooseTarget': 'Choose a target',
    'download.autoWidth': 'Auto',
    'download.batchExportPremium': 'Multi-export is available with a premium license.',
    'download.hdExportPremium': 'High-resolution export is available with a premium license.',
    'license.planLabel': 'Plan',
    'license.plan.free': 'Free',
    'license.plan.starter': 'Starter',
    'license.plan.pro': 'Pro',
    'license.plan.agency': 'Agency',
    'license.exportQuota': 'Export quota',
    'license.providerQuotaLabel': 'Provider quota',
    'license.exportsRemainingInline': '{remaining} left / {limit} per period',
    'license.exportsUsedInline': '{used} used / {limit} per period',
    'license.exportsReached': 'Your export quota for the current period has been reached.',
    'license.exportsUsage': 'This plan currently uses {used} exports out of {limit} for the active period.',
    'license.providerQuotaReached': 'Your external image provider quota for the current period has been reached.',
    'license.providerQuotaUsage': 'External providers are using {used} requests out of {limit} for the active period.',
    'license.providerSpecificQuotaReached': 'This provider quota has been reached for the current period.',
    'license.providerSpecificQuotaUsage': 'This provider currently uses {used} requests out of {limit} for the active period.',
    'license.statusLabel': 'Status',
    'license.status.trial_active': 'Trial active',
    'license.status.active': 'Active',
    'license.status.grace': 'Grace period',
    'license.status.expired': 'Expired',
    'license.status.invalid': 'Inactive',
    'license.status.revoked': 'Revoked',
    'license.renewsOn': 'Current period ends on {date}',
    'license.premiumFeatures': 'Premium features',
    'license.availableNow': 'Included',
    'license.requiresPlan': 'Included in {plan}',
    'license.quotaReachedShort': 'Quota reached',
    'license.configureIntegration': 'Configure integration',
    'license.disabledByPermissions': 'Disabled by permissions',
    'license.imageSourcesTitle': 'Image sources',
    'license.premiumSourcesTitle': 'Premium sources',
    'license.upgradeToUnlockSources': 'Unlock external libraries and premium providers with your paid plan.',
    'license.studioAdvancedTitle': 'Advanced Studio tools',
    'license.studioAdvancedHint': 'Kernels and Remove colors stay available as soon as Studio Advanced is enabled.',
    'license.refreshesOn': 'Quota resets on {date}',
    'license.currentLicense': 'Current license',
    'license.activationTitle': 'Activation',
    'license.activationHint': 'Paste your commercial key to unlock the premium runtime on this instance.',
    'license.remoteReady': 'This integration can resolve a license key directly from the client runtime.',
    'license.remoteMissing': 'No remote license resolver is configured yet in this integration. You can still inspect the current status here.',
    'license.keyLabel': 'License key',
    'license.keyStored': 'A key is already configured for refresh. Pasting a new one here replaces it for the current session.',
    'license.storedShort': 'Stored',
    'license.activate': 'Activate license',
    'license.refreshLicense': 'Refresh license',
    'license.useFreeMode': 'Use free mode',
    'license.deactivateLicense': 'Deactivate instance',
    'license.activationSuccess': 'License activated successfully.',
    'license.activationPartial': 'The license was checked, but premium features are still unavailable with the current response.',
    'license.activationFailed': 'Unable to activate this license right now.',
    'license.refreshSuccess': 'License status refreshed.',
    'license.refreshFailed': 'Unable to refresh the license status.',
    'license.resetSuccess': 'Local activation data cleared. The designer is now using free mode.',
    'license.resetFailed': 'Unable to clear the local activation data.',
    'license.deactivateSuccess': 'License deactivated for this instance.',
    'license.deactivateFailed': 'The current integration cannot deactivate this instance yet.',
    'license.enterKeyFirst': 'Enter a license key before activating premium features.',
    'license.sourceLabel': 'Source',
    'license.source.none': 'No input',
    'license.source.key': 'License key',
    'license.source.token': 'Signed token',
    'license.source.entitlements': 'Manual entitlements',
    'license.domainLabel': 'Domain',
    'license.instanceIdLabel': 'Instance',
    'license.enabledFeaturesTitle': 'Enabled features',
    'license.noPremiumFeatures': 'No premium features are enabled on this instance yet.',
    'license.manageLicense': 'Manage license',
    'license.activateToUnlock': 'Activate your premium plan to unlock this module section.',
    facebook_token_missing_placeholder: 'Token missing',
    upper: 'UPPER',
    lower: 'lower',
    collapse_top_bar: 'Collapse top bar',
    expand_top_bar: 'Expand top bar',
    collapse_left_sidebar: 'Collapse left sidebar',
    expand_left_sidebar: 'Expand left sidebar',
    collapse_right_sidebar: 'Collapse right sidebar',
    expand_right_sidebar: 'Expand right sidebar',
    collapse_bottom_bar: 'Collapse bottom bar',
    expand_bottom_bar: 'Expand bottom bar',
    new_text: 'New text',
    panels: 'PANELS',
    properties: 'PROPERTIES',
    front: 'Front',


},
  // Flat fallback keys generated by the i18n coverage pass.
  "branding.asukaDesigner": "Asuka Designer",
  "branding.poweredBy": "Powered by",
  "branding.poweredByAsukaDesigner": "Powered by Asuka Designer",
  "carousel.page": "Page {page}",
  "colors.notAllowed": "Color editing is not allowed.",
  "colors.useColorPickerNoPalette": "Use the color picker when no palette is configured.",
  "common.active": "Active",
  "common.auto": "Auto",
  "common.bringToFront": "Bring to front",
  "common.clear": "Clear",
  "common.disabled": "Disabled",
  "common.dragToReorder": "Drag to reorder",
  "common.dropHere": "Drop here",
  "common.duplicate": "Duplicate",
  "common.empty": "Empty",
  "common.enabled": "Enabled",
  "common.error": "Something went wrong.",
  "common.fill": "Fill",
  "common.icon": "Icon",
  "common.image": "Image",
  "common.invalid": "Invalid value",
  "common.move": "Move",
  "common.name": "Name",
  "common.noItems": "No items.",
  "common.noPreview": "No preview",
  "common.price": "Price",
  "common.redo": "Redo",
  "common.reorder": "Reorder",
  "common.stroke": "Stroke",
  "common.submit": "Submit",
  "common.undo": "Undo",
  "common.unlimited": "Unlimited",
  "common.width": "Width",
  "download.allAreas": "All drawing areas",
  "download.allObjects": "All layers and elements",
  "download.allViews": "All views",
  "download.autoWidth": "Auto",
  "download.batchExportPremium": "Exporting multiple items is available with production export.",
  "download.bleed": "Bleed",
  "download.canvas": "Canvas",
  "download.chooseTarget": "Choose a target",
  "download.cropMarks": "Crop marks",
  "download.cropMarksOffset": "Offset",
  "download.cropMarksUnit": "Unit",
  "download.currentView": "Current view",
  "download.export": "Download",
  "download.exportFailed": "Export failed. Please try again.",
  "download.exportQuality": "Export quality",
  "download.format": "Format",
  "download.layersElements": "Layers and elements",
  "download.margin": "Margin",
  "download.mode": "Mode",
  "download.pdfSupportLoadsAutomatically": "PDF support will load automatically when you export.",
  "download.pdfUnavailable": "PDF export is not available right now.",
  "download.premiumExportUnavailable": "This production export is not available right now.",
  "download.printReady": "Print-ready",
  "download.productionExports": "Production export",
  "download.productionExportsHint": "Export standard files, print-ready files, PDF, or a server-rendered HD file when available.",
  "download.scope": "Scope",
  "download.serverHdExportSimple": "Use server HD render",
  "download.serverHdExportSimpleHint": "Render the active 2D or 3D view on the protected server for the highest production quality.",
  "download.serverHdSingleOnly": "Server HD render is available for one export at a time.",
  "download.singleArea": "Single area",
  "download.singleObject": "Single object",
  "download.singleView": "Single view",
  "download.standardExport": "Standard export",
  "download.standardExportHint": "Export standard image files for preview and everyday use.",
  "download.standardQuality": "Standard",
  "download.target": "Target",
  "download.title": "Download",
  "download.unit": "Unit",
  "download.views": "Views",
  "download.zip": "ZIP multi-files",
  "drawing.areas": "Drawing areas",
  "drawingAreas.clipOff": "Clip off",
  "drawingAreas.clipOn": "Clip on",
  "drawingAreas.creationHint": "Pick a quick shape, draw directly on the canvas, or import an SVG mask.",
  "drawingAreas.creationDisabled": "Drawing area creation is disabled for this mode.",
  "drawingAreas.creationTitle": "Create a drawing area",
  "drawingAreas.draftConversionHint": "After creating the draft, place or resize it on the canvas. The designer will convert it into a structural drawing area.",
  "drawingAreas.drawHint": "Choose a shape, then drag on the canvas to define the area.",
  "drawingAreas.dropSvgFile": "Drop an SVG file here",
  "drawingAreas.dropSvgHint": "or browse your files to import an SVG mask",
  "drawingAreas.createSvgArea": "Create drawing area",
  "drawingAreas.existingAreasHint": "Manage the structural zones already available on this canvas.",
  "drawingAreas.importingSvg": "Importing SVG…",
  "drawingAreas.maxObjects": "Max objects",
  "drawingAreas.noAreasHint": "Create a quick shape, draw one on the canvas, or import an SVG area.",
  "drawingAreas.noAreasYet": "No drawing areas yet.",
  "drawingAreas.objects": "objects",
  "drawingAreas.quickShapesHint": "Create a ready-to-edit draft in the center of the canvas.",
  "drawingAreas.svgImport": "SVG import",
  "drawingAreas.svgImportHint": "Drop an SVG file or paste SVG markup to create a drawing area at the SVG dimensions.",
  "drawingAreas.title": "Drawing areas",
  "elements.add": "Add element",
  "elements.apply": "Apply",
  "elements.applyVariation": "Apply this variation",
  "elements.copy.variationId": "Variation ID",
  "elements.currentViewOnly": "Current view only",
  "elements.details.helper": "Define the base metadata and media used across all variations.",
  "elements.details.title": "Element details",
  "elements.edit": "Edit element",
  "elements.editor.subtitle": "Create reusable catalog elements with clean metadata, media and variations.",
  "elements.editor.title": "Element editor",
  "elements.emptyDescription": "Add an element or copy one from another view to start editing variations.",
  "elements.emptyTitle": "No elements in this view",
  "elements.form.active": "Active",
  "elements.form.category": "Category",
  "elements.form.evented": "Evented",
  "elements.form.selectable": "Selectable",
  "elements.form.variations": "Variations",
  "elements.inView": "In view",
  "elements.noElementSelected": "No element selected.",
  "elements.noSelectableVariationsYet": "This element does not have selectable variations yet.",
  "elements.noVariations": "No variations",
  "elements.noVariationsAvailable": "No variations available",
  "elements.open": "Open",
  "elements.selectElement": "Select element",
  "elements.selectElementForVariations": "Select an element in the current view to edit its variations.",
  "elements.openGroup": "Open this group",
  "elements.options.helper": "Choose how the element behaves when added to the canvas.",
  "elements.options.title": "Behavior",
  "elements.title": "Elements",
  "elements.unnamed": "Element",
  "elements.variation": "Variation",
  "elements.variations.count": "At least one variation required",
  "elements.variations.optional": "Variations optional",
  "elements.variations.helper": "Add one or more sellable variations. Mark a single active variation as the default leaf.",
  "facebook.connectHint": "Connect your Facebook account to browse your photos and albums.",
  "facebook.graphApiHint": "This uses Facebook Login and the Graph API.",
  "facebook.notConfiguredPrefix": "Facebook integration is not configured. Pass",
  "facebook.notConfiguredSuffix": "at initialization to enable this tab.",
  "fileDropzone.browseFile": "Browse file",
  "fileDropzone.label": "Click or drop files here",
  "fixedSlots.addMarkerHint": "Configure the marker, then place it on the canvas.",
  "fixedSlots.allowedValues": "Allowed values",
  "fixedSlots.appearanceAndReplacement": "Appearance & replacement",
  "fixedSlots.behavior": "Behavior",
  "fixedSlots.editMarkerHint": "Update this marker definition and its placement constraints.",
  "fixedSlots.emptyHint": "Add a marker, then click the canvas to place it.",
  "fixedSlots.fixedMarkers": "Fixed markers",
  "fixedSlots.height": "Height",
  "fixedSlots.markerColor": "Marker color",
  "fixedSlots.markerInfo": "Marker info",
  "fixedSlots.markerList": "Marker list",
  "fixedSlots.placementActive": "Placement mode active",
  "fixedSlots.sizeLimits": "Size limits",
  "fixedSlots.startPlacement": "Start placement",
  "fixedSlots.width": "Width",
  "form.validation.atLeastOne": "You must set at least one active item",
  "form.validation.required": "This field is required",
  "form.validation.unique": "Only one item can be active",
  "history.maxSnapshots": "Max",
  "image.aiAddFailed": "The generated image could not be added to the canvas.",
  "image.aiAspectRatio": "Format",
  "image.aiCreateHint": "Describe what you want to generate. When the image is ready, add it to your canvas.",
  "image.aiCreateTitle": "Create an image",
  "image.aiGenerate": "Generate",
  "image.aiGeneratedStatus": "Image ready. Choose one below.",
  "image.aiGenerating": "Generating…",
  "image.aiGeneratingStatus": "Generating image…",
  "image.aiModuleLabel": "AI image",
  "image.aiNegativePrompt": "Avoid",
  "image.aiNegativePromptPlaceholder": "Optional: blurry, low quality, cropped",
  "image.aiPrompt": "Description",
  "image.aiPromptPlaceholder": "Example: A clean mockup of a white t-shirt floating on a soft neutral background",
  "image.aiPromptRequired": "Write a prompt before generating an image.",
  "image.aiResultAlt": "Generated image",
  "image.aiStyle3dRender": "3D render",
  "image.aiStyleDefault": "Default",
  "image.aiStyleFlatDesign": "Flat design",
  "image.aiStyleIllustration": "Illustration",
  "image.aiStylePhotorealistic": "Photorealistic",
  "image.aiStylePreset": "Style",
  "image.aiStyleWatercolor": "Watercolor",
  "layers.deleteLayer": "Delete layer",
  "layers.notAvailable": "Layers are not available.",
  "license.activate": "Activate license",
  "license.activateToUnlock": "Activate your premium plan to unlock this module section.",
  "license.activationFailed": "Unable to activate this license right now.",
  "license.activationHint": "Paste your commercial key to unlock the premium runtime on this instance.",
  "license.activationPartial": "The license was checked, but premium features are still unavailable with the current response.",
  "license.activationSuccess": "License activated successfully.",
  "license.activationTitle": "Activation",
  "license.aiCreditsQuota": "AI credits",
  "license.aiCreditsReached": "No AI credits are remaining for the current period.",
  "license.aiCreditsRequired": "A Pro or Agency plan is required to use AI image generation.",
  "license.available": "Available",
  "license.configured": "Configured",
  "license.currentLicense": "Current license",
  "license.deactivateFailed": "The current integration cannot deactivate this instance yet.",
  "license.deactivateLicense": "Deactivate instance",
  "license.deactivateSuccess": "License deactivated for this instance.",
  "license.domainLabel": "Domain",
  "license.enabledFeaturesTitle": "Enabled features",
  "license.enterKeyFirst": "Enter a license key before activating premium features.",
  "license.exportQuota": "Export quota",
  "license.features.advancedPermissions.label": "Advanced permissions",
  "license.features.advancedPricing.label": "Advanced pricing",
  "license.features.aiCredits.label": "AI credits",
  "license.features.apiAdvanced.label": "Advanced API",
  "license.features.backgroundRemoval.label": "Background removal",
  "license.features.basicDesigns.label": "Basic designs",
  "license.features.basicEditor.label": "Core editor",
  "license.features.batchExport.label": "Batch export",
  "license.features.colors.label": "Colors",
  "license.features.designCatalog.label": "Design catalog",
  "license.features.drawingAreas.label": "Drawing areas",
  "license.features.facebookImages.label": "Facebook provider",
  "license.features.fixedSlots.label": "Fixed slots",
  "license.features.modelOptimization.label": "GLB optimization",
  "license.features.hdExport.label": "HD export",
  "license.features.history.label": "History",
  "license.features.imageUpload.label": "Image upload",
  "license.features.layers.label": "Layers",
  "license.features.pexels.label": "Pexels provider",
  "license.features.pixabay.label": "Pixabay provider",
  "license.features.premiumTemplates.label": "Premium templates",
  "license.features.pricing.label": "Pricing summary",
  "license.features.pricingBasic.label": "Basic pricing",
  "license.features.pricingRules.label": "Pricing rules",
  "license.features.printingMethods.label": "Printing methods",
  "license.features.qrcodeImages.label": "QR code images",
  "license.features.shapes.label": "Shapes",
  "license.features.standardExport.label": "Standard export",
  "license.features.studioAdvanced.label": "Advanced Studio",
  "license.features.studioBasic.label": "Basic Studio",
  "license.features.text.label": "Text editing",
  "license.features.transparentExport.label": "Transparent export",
  "license.features.views.label": "Views",
  "license.features.whiteLabel.label": "White label",
  "license.features.zoom.label": "Zoom tools",
  "license.httpReady": "HTTP ready",
  "license.instanceIdLabel": "Instance",
  "license.keyLabel": "License key",
  "license.keyPlaceholder": "ASK-XXXX-XXXX-XXXX",
  "license.keyStatusLabel": "Key",
  "license.keyStored": "A key is already configured for refresh. Pasting a new one here replaces it for the current session.",
  "license.manageLicense": "Manage license",
  "license.missing": "Missing",
  "license.noPremiumFeatures": "No premium features are enabled on this instance yet.",
  "license.notConfigured": "Not configured",
  "license.plan.pro": "Pro",
  "license.refreshFailed": "Unable to refresh the license status.",
  "license.refreshLicense": "Refresh license",
  "license.refreshSuccess": "License status refreshed.",
  "license.remoteMissing": "No remote license resolver is configured yet in this integration. You can still inspect the current status here.",
  "license.remoteReady": "This integration can resolve a license key directly from the client runtime.",
  "license.resetFailed": "Unable to clear the local activation data.",
  "license.resetSuccess": "Local activation data cleared. The designer is now using free mode.",
  "license.resolverLabel": "Resolver",
  "license.sourceLabel": "Source",
  "license.storedShort": "Stored",
  "license.studioAdvancedHint": "Kernels and Remove colors stay available as soon as Studio Advanced is enabled.",
  "license.studioAdvancedTitle": "Advanced Studio tools",
  "license.tokenStatusLabel": "Token",
  "license.useFreeMode": "Use free mode",
  "overlay.dropIn": "Drop in",
  "overlay.popOut": "Pop out",
  "permissions.denied": "Permission denied",
  "pricing.basePriceHint": "Your product base price (before objects & rules)",
  "pricing.lastRefresh": "Last refresh:",
  "pricingRules.advancedJson": "Advanced (JSON)",
  "pricingRules.aggregations.first": "First matching tier",
  "pricingRules.aggregations.highest": "Highest surcharge",
  "pricingRules.aggregations.label": "Matching tiers",
  "pricingRules.aggregations.sum": "Sum matching tiers",
  "pricingRules.details.displayedInList": "Displayed in the list",
  "pricingRules.details.emptyTypesMatchEverything": "Leave empty to match everything",
  "pricingRules.details.emptyViewsMatchAll": "Leave empty to match all views",
  "pricingRules.handlers.canvasSize": "Canvas size",
  "pricingRules.handlers.layerCoverage": "Layer coverage",
  "pricingRules.handlers.layerSize": "Layer size",
  "pricingRules.handlers.layersAmount": "Layers amount",
  "pricingRules.handlers.textLength": "Text length",
  "pricingRules.handlers.textLines": "Text lines",
  "pricingRules.handlers.usedColorsAmount": "Used colors amount",
  "pricingRules.handlers.printingMethodsAmount": "Printing methods amount",
  "pricingRules.heightPercent": "Height (%)",
  "pricingRules.loadingModule": "Loading rules module…",
  "pricingRules.matches.all": "All conditions",
  "pricingRules.matches.any": "Any condition",
  "pricingRules.matches.label": "Width / height logic",
  "pricingRules.measurements.label": "Measure",
  "pricingRules.measurements.native": "Native size",
  "pricingRules.measurements.percent": "% of drawing area",
  "pricingRules.operators.modulo": "% (step)",
  "pricingRules.scopes.all": "All matches",
  "pricingRules.scopes.first": "First match",
  "pricingRules.summary.condition": "When {handler} {operator} {value} → +{price}",
  "pricingRules.summary.noCondition": "When {handler} → (no condition)",
  "pricingRules.targetTypes.design": "Design",
  "pricingRules.targetTypes.elements": "Product elements",
  "pricingRules.targetTypes.image": "Image",
  "pricingRules.targetTypes.layers": "Layers",
  "pricingRules.targetTypes.text": "Text",
  "pricingRules.targetTypes.tools": "Tools",
  "pricingRules.widthPercent": "Width (%)",
  "printing.allMethods": "All methods",
  "qrcode.ecLevelHigh": "H (30%)",
  "qrcode.ecLevelLow": "L (7%)",
  "qrcode.ecLevelMedium": "M (15%)",
  "qrcode.ecLevelQuartile": "Q (25%)",
  "shapes.addShape": "Add {shape}",
  "shapes.cancelDrawing": "Cancel drawing",
  "shapes.circle": "Circle",
  "shapes.circleHelper": "Rounded shape for badges",
  "shapes.drawShape": "Draw {shape}",
  "shapes.line": "Line",
  "shapes.lineHelper": "Divider or underline",
  "shapes.rect": "Rectangle",
  "shapes.rectHelper": "Classic block shape",
  "shapes.star": "Star",
  "shapes.starHelper": "Accent and highlight shape",
  "shapes.triangle": "Triangle",
  "shapes.triangleHelper": "Strong directional shape",
  "state.clearStorage": "Clear storage",
  "state.export": "State export",
  "state.import": "State import",
  "state.importFile": "Import file",
  "state.loadFromStorage": "Load storage",
  "state.saveToStorage": "Save storage",
  "state.scope.colorScope": "Color scope",
  "state.scope.fixedSlots": "Markers",
  "state.scope.fonts": "Fonts",
  "state.scope.integrations": "Integrations",
  "state.scope.listeners": "Listeners",
  "state.scope.magnifier": "Magnifier",
  "state.scope.moduleConfig": "Module config",
  "state.scope.pricingBasePrice": "Base price",
  "state.scope.pricingRules": "Pricing rules",
  "state.scope.pricingTotals": "Pricing totals",
  "state.scope.ruler": "Ruler",
  "state.scope.selectionMenu": "Selection menu",
  "state.scope.shapes": "Shapes",
  "studio.adjust": "Adjust",
  "studio.aiBackgroundRemoval": "Remove background",
  "studio.aiBackgroundRemovalComingSoon": "Background removal is not available right now.",
  "studio.aiBackgroundRemovalHint": "Clean up the selected image in the Studio preview.",
  "studio.aiBackgroundRemoveAction": "Remove background",
  "studio.aiBackgroundRemovedStatus": "Background removed. Apply when ready.",
  "studio.aiBackgroundRemoving": "Removing…",
  "studio.aiBackgroundRemovingStatus": "Removing background…",
  "studio.applyToSelection": "Apply to selection",
  "studio.backgroundRemovalImagesOnly": "Background removal applies to images only.",
  "studio.brightness": "Brightness",
  "studio.canvasSizeAdapts": "Canvas size adapts to selected object.",
  "studio.colorMatrix": "Color matrix",
  "studio.contrast": "Contrast",
  "studio.convolution": "Convolution",
  "studio.designsHint": "Add designs (SVG or images) inside the Studio sandbox canvas.",
  "studio.filters": "Filters",
  "studio.filtersImagesOnly": "Filters apply to images only.",
  "studio.flipX": "Flip X",
  "studio.flipY": "Flip Y",
  "studio.gamma": "Gamma",
  "studio.grayscale": "Grayscale",
  "studio.hue": "Hue",
  "studio.invert": "Invert",
  "studio.kernels": "Kernels",
  "studio.kernelsImagesOnly": "Kernels apply to images only.",
  "studio.lookPresets": "Look presets",
  "studio.noColorsRemovedYet": "No colors removed yet. Click Add.",
  "studio.noise": "Noise",
  "studio.pixelate": "Pixelate",
  "studio.presetsImagesOnly": "Presets apply to images only.",
  "studio.previewOnlyHint": "Image: filters/presets/kernels apply to the Studio preview only (then Apply).",
  "studio.presets": "Presets",
  "studio.settings": "Settings",
  "studio.previewSettings": "Preview settings",
  "studio.regroupAllStudio": "Regroup all (Studio)",
  "studio.regroupAllTip": "Tip: Regroup all works even when no parts are selected.",
  "studio.removeColors": "Remove colors",
  "studio.removeColorsTip": "Tip: you can remove multiple colors. Works on images via filters; SVG removal depends on how the SVG is constructed.",
  "studio.resetFilters": "Reset Filters",
  "studio.resetLook": "Reset Look",
  "studio.saturation": "Saturation",
  "studio.selectImageOrSvg": "Select an image or an SVG object to open Studio.",
  "studio.selectSvgGroupToEditParts": "Select an SVG/group to edit its parts.",
  "studio.sepia": "Sepia",
  "studio.svgPartsEditor": "SVG parts editor",
  "studio.textHint": "Add/edit text inside the Studio sandbox canvas.",
  "studio.title": "Image & SVG editor (preview + apply)",
  "studio.toggles": "Toggles",
  "studio.ungroupDesignHint": "Ungroup the design, then select one or more parts to edit fill and stroke.",
  "studio.ungroupStudio": "Ungroup (Studio)",
  "studio.vibrance": "Vibrance",
  "textCurve.archDown": "Arch down",
  "textCurve.archLeft": "Arch left",
  "textCurve.archRight": "Arch right",
  "textCurve.archUp": "Arch up",
  "textCurve.bendValue": "Curve strength",
  "textCurve.circle": "Circle",
  "textCurve.deflate": "Deflate",
  "textCurve.inflate": "Inflate",
  "textCurve.makeCurved": "Curve text",
  "textCurve.makeStraight": "Make straight",
  "textCurve.title": "Curved text",
  "textCurve.wave": "Wave",
  "transform.downShort": "↓",
  "transform.empty": "Select an object to edit its position and rotation.",
  "transform.leftShort": "←",
  "transform.position": "Position",
  "transform.rightShort": "→",
  "transform.selectedObject": "Selected",
  "transform.singleObjectOnly": "Select a single object to edit its exact position and rotation.",
  "transform.upShort": "↑",
  "ui.case": "Case",
  "ui.choose_custom_color": "Choose a custom color.",
  "ui.createFirstView": "Create a view",
  "ui.createView": "Create a view",
  "ui.layout_drag_disabled": "Menu drag and drop is disabled by permissions.",
  "ui.loadingAsukaDesigner": "Loading AsukaDesigner",
  "ui.noViewsBody": "Create a view to start designing.",
  "ui.noViewsNoPermission": "You don't have permission to create views.",
  "ui.noViewsTitle": "No views yet",
  "ui.spacing": "Spacing",
  "ui.typography": "Typography",
  "units.centimeters": "Centimeters",
  "units.inches": "Inches",
  "units.millimeters": "Millimeters",
  "units.pixels": "Pixels",
  "views.blankView": "Blank view",
  "views.creating": "Creating…",
  "views.defaultNameFront": "Front",
  "views.fromElement": "From element",
  "views.fromElement.addFailed": "The element could not be added to the new view.",
  "views.fromElement.choose": "Choose an element",
  "views.fromElement.chooseFirst": "Choose an element before creating the view.",
  "views.fromElement.createFailed": "Unable to create this view from the element.",
  "views.fromElement.empty": "Create a 2D element with an image before using this option.",
  "views.fromElement.hint": "The view uses the image’s native pixel dimensions, like opening an image in an editor.",
  "views.fromElement.label": "Element",
  "views.fromElement.measureFailed": "The image dimensions could not be detected.",
  "views.fromElement.measuring": "Reading image dimensions…",
  "views.fromElement.ready": "The view will be {width} × {height} px and the element will keep its native size.",
  "views.fromElement.rendererUnavailable": "The 2D renderer is not ready.",
  "views.fromElement.unavailable": "This element does not contain a usable 2D image.",
  "views.presets.a4": "A4 (210×297 mm)",
  "views.presets.a5": "A5 (148×210 mm)",
  "views.presets.custom": "Custom",
  "views.presets.hd": "HD (1920×1080 px)",
  "views.presets.ig1080": "Instagram (1080×1080 px)",
  "views.presets.letter": "Letter (8.5×11 in)",
  "views.presets.photo10x15": "Photo (10×15 cm)",
  "views.presets.square30": "Square (30×30 cm)",
  "zoom.notAllowed": "Zoom tools are not allowed.",

  i18n: {
    missing: 'Missing translation: {key}',
  },

};

let singleton: I18nApi | null = null;

export function createI18n(init?: I18nInit): I18nApi {
  const warnOnMissing = !!init?.warnOnMissing;
  const missingOnce = new Set<string>();

  let locale = (init?.locale ?? 'en').trim() || 'en';
  let fallbackLocale = (init?.fallbackLocale ?? 'en').trim() || 'en';

  let bundles: Record<string, Translations> = {
    en: EN_TRANSLATIONS,
  };

  if (init?.translations && isObject(init.translations)) {
    Object.keys(init.translations).forEach((loc) => {
      bundles[loc] = deepMerge(bundles[loc] ?? ({} as any), (init.translations as any)[loc]);
    });
  }

  const api: I18nApi = {
    t(key, defaultValueOrParams, maybeParams) {
      const defaultValue = typeof defaultValueOrParams === 'string' ? defaultValueOrParams : undefined;
      const params = (typeof defaultValueOrParams === 'object' && defaultValueOrParams) ? defaultValueOrParams : maybeParams;

      const raw =
        getPath(bundles[locale], key) ??
        getPath(bundles[fallbackLocale], key) ??
        (defaultValue ?? undefined);

      if (raw === undefined) {
        if (warnOnMissing) {
          const sig = `${locale}|${fallbackLocale}|${key}`;
          if (!missingOnce.has(sig)) {
            missingOnce.add(sig);

            void 0;
          }
        }
        return interpolate(getPath(bundles[fallbackLocale], 'i18n.missing') ?? key, { key });
      }

      // If we had to fall back to defaultValue, optionally warn (dev only, when warnOnMissing enabled)
      if (warnOnMissing && defaultValue !== undefined && raw === defaultValue) {
        const sig = `${locale}|${fallbackLocale}|${key}|default`;
        if (!missingOnce.has(sig)) {
          missingOnce.add(sig);

          void 0;
        }
      }

      return interpolate(raw, params);
    },

    getLocale() {
      return locale;
    },

    setLocale(next) {
      const v = (next ?? '').trim();
      if (!v) return;
      locale = v;
    },

    getFallbackLocale() {
      return fallbackLocale;
    },

    setFallbackLocale(next) {
      const v = (next ?? '').trim();
      if (!v) return;
      fallbackLocale = v;
    },

    addTranslations(loc, bundle) {
      const l = (loc ?? '').trim();
      if (!l || !bundle) return;
      bundles[l] = deepMerge(bundles[l] ?? ({} as any), bundle);
    },

    getTranslations() {
      return bundles;
    },
  };

  return api;
}

/** Singleton i18n instance used by the designer. */
export function initI18n(init?: I18nInit): I18nApi {
  // Recreate the singleton on each explicit initialization so runtime
  // consumers always see the latest locale, fallback locale and bundles
  // passed at boot time.
  singleton = createI18n(init);
  return singleton;
}

export function getI18n(): I18nApi {
  if (!singleton) singleton = createI18n({});
  return singleton;
}
