- 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>
293 lines
8.9 KiB
Markdown
293 lines
8.9 KiB
Markdown
# 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
|
|
|
|
### Footer Component
|
|
- **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
|
|
```bash
|
|
$ 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)
|
|
```
|
|
|
|
### 2. Footer Rendering Confirmed
|
|
```javascript
|
|
[Footer] Footer exists: true
|
|
[Footer] Elements with data-i18n: 20
|
|
```
|
|
Footer HTML is in DOM with proper `data-i18n` attributes.
|
|
|
|
### 3. Translation Application Called
|
|
```javascript
|
|
[Footer] Translations applied
|
|
```
|
|
`window.I18n.applyTranslations()` executes without errors.
|
|
|
|
### 4. Translation Fails
|
|
```javascript
|
|
[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
|
|
```javascript
|
|
// /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:**
|
|
```javascript
|
|
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:**
|
|
```javascript
|
|
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.js` → `language-selector.js` → `scroll-animations.js` → `page-transitions.js` → `footer.js`
|
|
- **Footer in page-specific JSON**: NO
|
|
- **Result**: Footer displays correctly
|
|
|
|
### Broken Pages (researcher.html, leader.html)
|
|
- **Script order**: `i18n-simple.js` → `language-selector.js` → `scroll-animations.js` → `page-transitions.js` → `version-manager.js` → `researcher-page.js` → `footer.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
|
|
|
|
### Fix 1: Consolidated footer translations (v1.1.8)
|
|
**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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// 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`):
|
|
```javascript
|
|
this.translations = { ...commonTranslations, ...pageTranslations };
|
|
```
|
|
|
|
**Problem**: Shallow merge overwrites entire top-level objects.
|
|
|
|
**Fix**: Deep merge utility
|
|
```javascript
|
|
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
|
|
|
|
### Solution C: Manual Footer Translation
|
|
**Instead of relying on global `applyTranslations()`:**
|
|
```javascript
|
|
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
|