tractatus/FOOTER_I18N_DIAGNOSTIC_BRIEF.md
TheFlow ac2db33732 fix(submissions): restructure Economist package and fix article display
- Create Economist SubmissionTracking package correctly:
  * mainArticle = full blog post content
  * coverLetter = 216-word SIR— letter
  * Links to blog post via blogPostId
- Archive 'Letter to The Economist' from blog posts (it's the cover letter)
- Fix date display on article cards (use published_at)
- Target publication already displaying via blue badge

Database changes:
- Make blogPostId optional in SubmissionTracking model
- Economist package ID: 68fa85ae49d4900e7f2ecd83
- Le Monde package ID: 68fa2abd2e6acd5691932150

Next: Enhanced modal with tabs, validation, export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 08:47:42 +13:00

8.9 KiB

Footer i18n Translation Failure - Technical Diagnostic Brief

Date: 2025-10-22 Issue: Footer displays raw translation keys instead of translated text on /researcher.html and /leader.html Status: BLOCKING - Prevents deployment Affected Pages: /researcher.html, /leader.html Working Pages: /index.html, /implementer.html


Problem Statement

Footer component renders correctly but translations fail to apply. Elements display raw keys like footer.about_heading instead of "Tractatus Framework".

Console Output Shows:

[Footer] Applying translations...
[Footer] Footer exists: true
[Footer] Elements with data-i18n: 20
[Footer] Translations applied
[Footer] about_heading element text: footer.about_heading  ← PROBLEM

Expected: about_heading element text: Tractatus Framework


System Architecture

Translation System

  • Library: Custom i18n implementation (/public/js/i18n-simple.js)
  • Translation files: /public/locales/en/common.json, /public/locales/en/researcher.json
  • Loading strategy: Async fetch → shallow merge
  • Application method: window.I18n.applyTranslations() replaces innerHTML of [data-i18n] elements
  • File: /public/js/components/footer.js
  • Type: Dynamically inserted component (not in static HTML)
  • Initialization: Event-based (waits for i18nInitialized event)
  • Cache version: ?v=1761129862

Diagnostic Evidence

1. Translation Data Verified on Server

$ curl -s http://localhost:9000/locales/en/common.json | jq '.footer.about_heading'
"Tractatus Framework"  ✓ CORRECT

$ curl -s http://localhost:9000/locales/en/researcher.json | jq 'has("footer")'
false  ✓ CORRECT (footer removed to prevent overwrite)
[Footer] Footer exists: true
[Footer] Elements with data-i18n: 20

Footer HTML is in DOM with proper data-i18n attributes.

3. Translation Application Called

[Footer] Translations applied

window.I18n.applyTranslations() executes without errors.

4. Translation Fails

[Footer] about_heading element text: footer.about_heading

After applyTranslations(), element still contains raw key.


Root Cause Hypothesis

window.I18n.t('footer.about_heading') is returning the key instead of the translation.

i18n.t() Implementation

// /public/js/i18n-simple.js:123-136
t(key) {
  const keys = key.split('.');
  let value = this.translations;

  for (const k of keys) {
    if (value && typeof value === 'object') {
      value = value[k];
    } else {
      return key; // Return key if translation not found
    }
  }

  return value || key;
}

Possible failures:

  1. this.translations doesn't have footer key
  2. this.translations.footer doesn't have about_heading key
  3. Value is not a string (e.g., still an object)
  4. Timing issue - translations not yet loaded when applyTranslations() called

Critical Question

What does window.I18n.translations contain when footer renders?

Diagnostic Step Required

In browser console, run:

console.log('Full translations:', window.I18n.translations);
console.log('Footer object:', window.I18n.translations.footer);
console.log('about_heading:', window.I18n.translations.footer?.about_heading);
console.log('Test t():', window.I18n.t('footer.about_heading'));

Expected output:

Full translations: {footer: {...}, page: {...}, header: {...}, sections: {...}}
Footer object: {about_heading: "Tractatus Framework", ...}
about_heading: "Tractatus Framework"
Test t(): "Tractatus Framework"

If output differs, this reveals where the data structure breaks.


Working vs Broken Comparison

Working Pages (index.html, implementer.html)

  • Script order: i18n-simple.jslanguage-selector.jsscroll-animations.jspage-transitions.jsfooter.js
  • Footer in page-specific JSON: NO
  • Result: Footer displays correctly

Broken Pages (researcher.html, leader.html)

  • Script order: i18n-simple.jslanguage-selector.jsscroll-animations.jspage-transitions.jsversion-manager.jsresearcher-page.jsfooter.js
  • Footer in page-specific JSON: Removed (was causing shallow merge issue)
  • Result: Footer displays raw keys

Key difference: Extra scripts (version-manager.js, researcher-page.js) load between i18n and footer.


Previous Fixes Attempted

Problem: researcher.json and leader.json had partial footer objects that overwrote common.json footer during shallow merge. Action: Moved all footer keys to common.json, removed from page-specific files. Result: Data structure fixed on server, but translations still not applied.

Fix 2: Event-based initialization (v1.1.9)

Problem: Race condition - footer rendering before i18n loaded. Action: Wait for i18nInitialized event before rendering. Result: Timing fixed (console confirms footer waits for i18n), but translations still fail.

Fix 3: Removed polling (v1.2.0)

Problem: Polling logic triggered rate limiting (100 req/15min). Action: Simplified to single event-based check. Result: Rate limiting resolved, but core translation issue persists.


Next Debugging Steps

Step 1: Inspect Browser Runtime State

// In browser console on /researcher.html after page load:
window.I18n.translations
window.I18n.t('footer.about_heading')
document.querySelector('footer [data-i18n="footer.about_heading"]').innerHTML

Step 2: Compare Working Page

// In browser console on /index.html after page load:
window.I18n.translations
window.I18n.t('footer.about_heading')
document.querySelector('footer [data-i18n="footer.about_heading"]').innerHTML

Step 3: Check Translation Merge

// Does researcher.json override common.json at runtime?
// Expected: common.json footer + researcher.json sections
// Actual: ???

Potential Solutions

Solution A: Deep Merge Instead of Shallow Merge

Current code (i18n-simple.js:111):

this.translations = { ...commonTranslations, ...pageTranslations };

Problem: Shallow merge overwrites entire top-level objects.

Fix: Deep merge utility

this.translations = deepMerge(commonTranslations, pageTranslations);

function deepMerge(target, source) {
  const output = { ...target };
  for (const key in source) {
    if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
      output[key] = deepMerge(target[key] || {}, source[key]);
    } else {
      output[key] = source[key];
    }
  }
  return output;
}

Solution B: Namespace Separation

Keep footer translations ONLY in common.json Never put footer keys in page-specific files Status: Already implemented, but verify runtime state

Instead of relying on global applyTranslations():

applyFooterTranslations() {
  const footerElements = document.querySelectorAll('footer [data-i18n]');
  footerElements.forEach(el => {
    const key = el.dataset.i18n;
    const translation = window.I18n.t(key);
    console.log(`Translating ${key} -> ${translation}`);
    el.innerHTML = translation;
  });
}

File Locations

Translation Files

  • /public/locales/en/common.json - Contains full footer object
  • /public/locales/en/researcher.json - No footer object (removed)
  • /public/locales/en/leader.json - No footer object (removed)

JavaScript Files

  • /public/js/i18n-simple.js - Translation system core
  • /public/js/components/footer.js - Footer component
  • /public/researcher.html:627 - Footer script tag with cache version
  • /public/leader.html:611 - Footer script tag with cache version

Server Configuration

  • /src/server.js:77 - Rate limiting middleware (100 req/15min)
  • /src/middleware/rate-limit.middleware.js - Rate limiter implementation

Success Criteria

Footer displays:

Tractatus Framework  (not footer.about_heading)
Documentation  (not footer.documentation_heading)
Framework Docs  (not footer.documentation_links.framework_docs)
...etc

Console shows:

[Footer] about_heading element text: Tractatus Framework

Contact for Clarification

Project Owner: Review console output from browser Required: Share window.I18n.translations object from browser console This will immediately reveal if the problem is:

  • Data not loaded (translations object missing footer)
  • Data structure wrong (footer exists but wrong shape)
  • Translation function broken (t() not navigating object correctly)
  • Application timing (translations loaded but not applied)

Priority: HIGH - Blocking deployment Estimated Debug Time: 15-30 minutes with browser console access Complexity: LOW - Simple data flow issue, just need to see runtime state