GSOC 2026 : Set up BookBrainz for internationalization

GSoC 2026: Set Up BookBrainz for Internationalization

Personal Information:

Name: Garv Thakre
Nickname: garv
Matrix handle: @garvthakre:matrix.org
Email: garvthakre0@gmail.com
GitHub: garvthakre
LinkedIn: linkedin.com/in/garv-thakre
Timezone: IST (UTC+5:30)

Introduction:

I’m Garv Thakre, a B.Tech student who loves building products and exploring technology and i started coding and then i started to work on different projects and anything that my senior told me too , and then i started to participated in the hackathons, our team is the runner-up in the national Hackathon hosted by IIITNR, we also won hackathon at IITBHILAI and also won a lot of contests too and i also completed the summer internship in my first year in NITR and then i joined the startup as a backend intern and so on .


Why Me:

I have been contributing to BookBrainz and ListenBrainz for the past few months. I am very familiar with the codebase. I have traced the full SSR pipeline from [generateProps()] /bookbrainz-site/src/server/helpers/props.ts through renderToString() to ReactDOM.hydrate(), which is exactly the pipeline this project modifies.

Working Prototype

I ran BookBrainz locally and wired i18next into the actual codebase on a public branch β€” not a separate demo app. The About page now renders in French end-to-end via Accept-Language detection.

While doing this I hit two bugs:

  1. storing the i18next instance in res.locals breaks JSON serialization in [escapeProps()](/bookbrainz-site/src/server/helpers/props.ts#25-32 (the client receives {}).

  2. renderToString() needs I18nextProvider or useTranslation() hooks return raw keys during SSR.

Both fixes are already reflected in the implementation below.


About page running on localhost β€” heading translated via Accept-Language: fr detection, remaining strings pending migration.

My Portfolio :

https://g-forge.vercel.app/ (currently Improving) Have a look at the project section that’s freaking cool .

Personal and Team Projects :

  • CollabGPT : Live Demo source code

  • Post90 β€” A tool for developers who struggle to post consistently on LinkedIn or X. Reads your GitHub commits and generates post ideas you can publish directly. Live demo Β· GitHub

  • CampusX β€” A collaborative project I have been working on for a while. GitHub

  • NexusAI : NEXUS is a local AI automation agent that translates English task descriptions into step-by-step execution plans, then carries them out by controlling your browser, desktop apps, files powered by Groq (free) or other LLM providers. Github

My merged and Open PRs (10+) across MetaBrainz projects :

My backend internship experience showcased designing databases, building system architecture, and delivering features end-to-end maps directly to this project. At my internship i also design the database , handles documenttion using swagger and also javascript and typescript is the primary language i worked on.


Project Summary:

Title: Set Up BookBrainz for Internationalization
Proposed Mentor: @mr_monkey
Project Length: 175 hours
Short Description: BookBrainz is currently only in English, which limits its reach to a global audience. The MusicBrainz internationalization documentation explicitly confirms this: β€œThe following official MetaBrainz projects aren’t translatable at the moment: BookBrainz, Cover Art Archive, and ListenBrainz.”
Expected Outcome: A full i18n infrastructure β€” i18next integrated with React SSR, locale detection middleware, a language selector dropdown in the navbar, English translation JSON files, ~600 strings migrated across ~60+ files, and a BookBrainz project on translations.metabrainz.org so volunteer translators can contribute immediately.


Implementation:

The user visits BookBrainz with their browser set to French. The Express server reads the Accept-Language header using the existing getAcceptedLanguageCodes() helper already in the codebase. It loads the French translation JSON, creates an i18next instance, and passes it to the React SSR renderer. The page renders in French on the server. The loaded translations are injected alongside the existing Redux props via [generateProps()]( /bookbrainz-site/src/server/helpers/props.ts#33-52) β€” so React hydration requires zero extra HTTP requests and the page stays in French with no flicker.

For new translations: a developer pushes a new English key to GitHub. A GitHub Action runs i18next-parser automatically and commits updated JSON. A webhook fires to translations.metabrainz.org, Weblate shows the key to volunteer translators, and Weblate automatically opens a PR with the translated JSON. A maintainer merges it and the translation is live.

Existing Codebase β€” What’s Already There

Before writing any code, I studied the repo carefully from the last december. The codebase already done some work:

src/server/helpers/i18n.ts β€” Already exports two functions:

// Parses Accept-Language header, returns sorted array of language objects
export function parseAcceptLanguage(acceptLanguage: string): AcceptedLanguage[]

// Wraps the above β€” takes the full Express request, returns ['fr', 'en', ...]
export function getAcceptedLanguageCodes(request: Request): string[]

src/server/helpers/props.ts β€” The generateProps() function already merges req.session, res.locals, and page-specific props into a single object that feeds both ReactDOMServer.renderToString() and the client hydration. I18n state (locale + resources) will be added here β€” in the same place all other shared state lives.

src/server/templates/target.js β€” The HTML template already injects Redux state as a <script id='props'> tag. The i18n resources travel through this same mechanism β€” no new injection pattern needed.

public/locales/ β€” Express already serves public/ as static files via app.use(express.static('public')) in app.js. No new route is needed for /locales/fr/common.json.

package.json β€” @cospired/i18n-iso-languages is already a dependency, confirming the codebase has been pointed toward i18n work before.

This means the infrastructure requires adding to existing patterns, not reinventing them.

The SSR Pipeline β€” How i18n Plugs In

Every BookBrainz page follows this exact 4-step flow:

Step 1: generateProps(req, res) merges session + res.locals + page props
Step 2: ReactDOMServer.renderToString(<Layout {...props}/>) β†’ HTML string
Step 3: target.js injects markup + serialized props into full HTML page
Step 4: Client reads props from DOM β†’ ReactDOM.hydrate()

I18n state (locale + resources) is injected at Step 1, so both renderToString (Step 2) and hydrate (Step 4) use the exact same language and translations. Hydration mismatches are prevented by design β€” not by patching.

File Structure

bookbrainz-site/
β”œβ”€β”€ public/
β”‚   └── locales/
β”‚       β”œβ”€β”€ en/                         ← English source (developer creates these)
β”‚       β”‚   β”œβ”€β”€ common.json             ← buttons, nav, labels
β”‚       β”‚   β”œβ”€β”€ entityEditor.json       ← alias, name, relationship editors
β”‚       β”‚   β”œβ”€β”€ pages.json              ← About, Help, FAQ, static pages
β”‚       β”‚   β”œβ”€β”€ entities.json           ← entity display pages
β”‚       β”‚   └── errors.json             ← error page messages
β”‚       β”œβ”€β”€ fr/                         ← seeded for testing
β”‚       └── de/                         ← seeded for CSS stress-testing
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ common/
β”‚   β”‚   └── i18n/
β”‚   β”‚       └── i18n.ts                 ← NEW: isomorphic initialiser
β”‚   β”œβ”€β”€ client/
β”‚   β”‚   └── components/
β”‚   β”‚       └── language-selector.tsx   ← NEW: navbar language dropdown
β”‚   β”œβ”€β”€ server/
β”‚   β”‚   └── helpers/
β”‚   β”‚       β”œβ”€β”€ i18n.ts                 ← EXISTS: unchanged
β”‚   β”‚       β”œβ”€β”€ props.ts                ← MODIFIED: add i18n to shared state
β”‚   β”‚       └── middleware.ts           ← MODIFIED: add i18nMiddleware
β”‚   └── templates/
β”‚       └── target.js                   ← MODIFIED: lang attr
β”‚
β”œβ”€β”€ i18next-parser.config.js            ← NEW: string extraction config
β”œβ”€β”€ .github/workflows/
β”‚   └── i18n-extract.yml               ← NEW: auto-extract on every push
└── package.json                        ← MODIFIED: i18next packages added

1. Install Packages

npm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector
npm install --save-dev i18next-parser

2. Create the Isomorphic i18next Initialiser

File: src/common/i18n/i18n.ts (new file)

export function createI18n(locale = 'en', resources?) {
  const instance = i18n.createInstance(); // fresh per request β€” no locale leaking
  instance.use(initReactI18next);

  const hasResources = resources && Object.keys(resources).length > 0;

  if (!hasResources && !isServer) {
    // Browser only enters this branch if server failed to inject resources.
    // In normal flow this never runs β€” resources always arrive via #props.
    instance.use(HttpBackend).use(LangDetector);
  }

  instance.init({
    fallbackLng: 'en',
    initImmediate: false, // synchronous init when resources are already in memory
    lng: locale,
    ns: ['common', 'entityEditor', 'pages', 'entities', 'errors'],
    ...(hasResources
      ? {resources}                                              // server OR client with injected data
      : {backend: {loadPath: '/locales/{{lng}}/{{ns}}.json'}}  // client-only fallback
    ),
  });
  return instance;
}

3. Add Locale Detection Middleware

File: src/server/helpers/middleware.ts (add function)

import {getAcceptedLanguageCodes} from './i18n'; // already exists
const SUPPORTED = ['en', 'fr', 'de']; // for eg

export function i18nMiddleware(req, res, next) {
 // Cookie takes priority - set when user explicitly picks a language from the dropdown.
// Falls back to Accept-language handles for the first-time visitors with no preference set.
const cookieLang = req.cookies?.bb_lang;
const [headerLang = 'en'] = getAcceptedLanguageCodes(req);
const preferred = cookieLang ?? headerLang;
  const locale = SUPPORTED.includes(preferred) ? preferred : 'en';

  const load = (ns: string) => {
    try {
      return JSON.parse(fs.readFileSync(
        path.join(process.cwd(), 'public', 'locales', locale, `${ns}.json`), 'utf8'
      ));
    } catch { return {}; } // missing file never breaks the site
  };

  const resources = {[locale]: {common: load('common'), entityEditor: load('entityEditor')}};

  res.locals.locale        = locale;
  res.locals.i18nResources = resources;
  // Note: do NOT store the i18next instance in res.locals β€” it is not serializable.
  // The client re-creates it from the plain {locale, resources} object in props.
  next();
}

Register in src/server/app.js:

app.use(i18nMiddleware);

4. Inject i18n State into the Shared Props Pipeline

File: src/server/helpers/props.ts (add two lines)

export function generateProps<T>(req: PassportRequest, res: Response, props?: T) {
  const baseObject: Record<string, unknown> = {};
  if (req.session?.mergeQueue) baseObject.mergeQueue = req.session.mergeQueue;

  const merged = Object.assign(baseObject, res.locals, props);

  // Set i18n AFTER Object.assign β€” res.locals spreads many keys and would overwrite
  // anything set beforehand. Setting last guarantees {locale, resources} is never lost.
  merged.i18n = {
    locale: res.locals.locale || 'en',
    resources: res.locals.i18nResources || {}
  };

  return merged;
}

The client reads this before ReactDOM.hydrate() β€” same locale, same translations, zero mismatch.

SSR note: ReactDOMServer.renderToString() must be wrapped with I18nextProvider so useTranslation() hooks have access to the i18next instance during server-side rendering. Without the provider, hooks return the raw key string instead of the translation.

const markup = ReactDOMServer.renderToString(
  <I18nextProvider i18n={createI18n(locale, resources)}>
    <Layout {...props}><PageComponent /></Layout>
  </I18nextProvider>
);

5. Update the HTML Template

File: src/server/templates/target.js

// Add locale to the parameter list, add lang attribute (WCAG 2.1 criterion 3.1.1):
export default ({title, markup, page, props, script, locale}) => `
  <!doctype html>
  <html lang="${locale || 'en'}">
  ...

6. Set Up Auto String Extraction (CI/CD)

File: i18next-parser.config.js

module.exports = {
  input:            ['src/**/*.{js,jsx,ts,tsx}'],
  output:           'public/locales/$LOCALE/$NAMESPACE.json',
  locales:          ['en'],
  defaultNamespace: 'common',
  sort:             true,
};

File: .github/workflows/i18n-extract.yml (new) β€” runs on every push to master, auto-commits updated JSON so Weblate always sees fresh keys:

name: Extract i18n Strings
on:
  push:
    branches: [master]
    paths: ['src/**']
jobs:
  extract:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: {node-version: '18'}
      - run: npm ci && npx i18next-parser
      - run: |
          git config user.name "github-actions[bot]"
          git add public/locales/
          git diff --cached --quiet || git commit -m "chore(i18n): update translation strings" && git push

7. Migrate Components (Bulk of the 175 Hours)

Pattern A β€” Function components (useTranslation hook):

alias-editor.js is the proof-of-concept β€” migrated first to verify the full pipeline:

// BEFORE:
<Modal.Title>Alias Editor</Modal.Title>
<Button onClick={onClose}>Close</Button>

// AFTER:
const {t} = useTranslation('entityEditor');
<Modal.Title>{t('aliasEditor.title')}</Modal.Title>
<Button onClick={onClose}>{t('button.close')}</Button>

Pattern B β€” Class components (withTranslation HOC, zero refactoring needed):

pager.js hooks unavailable, so withTranslation wraps it and injects t as a prop:

// BEFORE:
<Button>&larr; Previous Page</Button>
<Button>Results {from + 1} β€” {from + size}</Button>
export default PagerElement;

// AFTER:
const {t} = this.props;
<Button>{t('pagination.previous')}</Button>
<Button>{t('pagination.results', {from: from + 1, to: from + size})}</Button>
export default withTranslation('common')(PagerElement);

{{from}} and {{to}} are i18next interpolation variables β€” translators keep the variable names and translate the surrounding words.

Migration scope:

Area Files Strings
Layout, Nav & Footer ~10 ~65
Static Pages (About, Help, FAQ, etc.) ~25 ~215
Entity Editor ~25 ~150
Entity Display ~25 ~80
Search, Revisions, Collections ~10 ~60
Errors & server-side titles ~25 ~50
Total ~120 ~620

8. Language Selector Dropdown

A visible user-facing deliverable β€” a language dropdown in the navbar using react-bootstrap. Saves the user’s choice in a cookie so the server renders in the correct language on the next visit.

// src/client/components/language-selector.tsx (new)
function LanguageSelector(): JSX.Element {
  const {i18n} = useTranslation();
  const handleChange = (code: string) => {
    document.cookie = `bb_lang=${code};path=/;max-age=31536000`;
    window.location.reload(); // server re-renders page in new language
  };
  return (
    <NavDropdown id="language-dropdown" title={<FontAwesomeIcon icon={faGlobe}/>}>
      {SUPPORTED_LANGUAGES.map(lang => (
        <NavDropdown.Item key={lang.code} onClick={() => handleChange(lang.code)}>
          {lang.name}
        </NavDropdown.Item>
      ))}
    </NavDropdown>
  );
}

9. Weblate Integration

After English JSON files exist on GitHub, the mentor creates the BookBrainz project on translations.metabrainz.org. From that point the GitHub Action keeps it in sync automatically:

GitHub Action extracts new strings β†’ commits to public/locales/en/
    ↓  Weblate detects update via webhook
Volunteer translators work in Weblate UI (no code, no GitHub account needed as per my knowledge)
    ↓  Weblate opens a PR: public/locales/fr/common.json
Maintainer reviews + merges β†’ French is live on BookBrainz

10. Developer Workflow for New Components

Four steps, documented in CONTRIBUTING.md:

Step 1 β€” Add the English string to the correct JSON file:

{ "myFeature.submitButton": "Submit Report" }

Step 2 β€” Use t() in the component:

const {t} = useTranslation('common');           // function component
const {t} = this.props;                         // class component via HOC
<Button>{t('myFeature.submitButton')}</Button>

Step 3 β€” Run the parser to verify:

npm run parse-i18n   #  myFeature.submitButton  stored in common.json

Step 4 β€” Push to GitHub. The GitHub Action and Weblate handle everything else automatically.


Risks & Mitigations

Risk Mitigation
SSR/hydration mismatch i18n state travels through generateProps() β€” server and client always use identical locale + resources
Missing locale file breaks the page try/catch in load() returns {} β€” site silently falls back to English, never crashes
Long translations breaking CSS German stress-test in Phase 7; apply overflow-wrap: break-word where needed
Translation keys going out of sync GitHub Action auto-extracts on every push to master
React 16 compatibility react-i18next v11+ supports React β‰₯ 16.8; withTranslation HOC works with any React version
Future contributors hardcoding strings 4-step CONTRIBUTING.md guide; GitHub Action catches missing keys on every CI run

Timeline:

Community Bonding (May):

Coding Phase 1 β€” Infrastructure (May 25 – May 31): ~15h

  • Install packages, create src/common/i18n/i18n.ts
  • Add i18nMiddleware, wire generateProps(), update target.js (lang attribute)
  • Unit tests for createI18n() and locale fallback
  • Set up GitHub Action for auto-extraction

Coding Phase 2 β€” Pipeline Verification (June 1 – June 7): ~12h

  • Migrate alias-editor.js as proof-of-concept (full round-trip in one component)
  • Coordinate with mentor to register on translations.metabrainz.org
  • Verify Weblate auto-PR cycle end-to-end with 3 test strings

Coding Phase 3 β€” Language Selector + String Extraction (June 8 – June 14): ~15h

  • Build language-selector.tsx, integrate into navbar with cookie persistence
  • Run full parser scan, document total string count across all namespaces

Coding Phase 4 β€” Entity Editor Migration (June 15 – June 28): ~30h

  • Week 1(June 15 - 21):
    • Migrate alias-editor, alias-row, name-section, name-section-merge.
  • Week 2 (June 22 - 28) :
    • Migrate identifier-editor, relationship-editor, annotation, button-bar, submission-section .
    • Populate entityEditor.json namespace .

Coding Phase 5 β€” Static Pages + Layout (June 29 – July 5): ~15h

  • Migrate About, Help, FAQ, Contribute, Privacy, error, registration pages
  • Migrate navigation (layout.js), footer, editor container
  • Write first draft of CONTRIBUTING.md

MidTerm Evaluation β€” (July 6 - July 10)

  • infrastructure complete, language selector shipped, entity editor fully migrated, ~350/600 strings done .

Coding Phase 6 β€” Entity Display + Unified Form + Controllers (July 10 – July 30): ~45h

  • Week 1 (July 10 - 16) :

    • Migrate entity display pages: Author, Work, Edition, Publisher, Series, Edition Group.
  • Week 2 (July 17-23) :

    • Migrate shared display components: identifiers, relationships, annotations .
    • search, revisions, collections pages and all 13 unified-form/ components (cover, detail, content, submit tabs).
  • Week 3(July 24-30) :

    • Migrate controllers with UI strings: search, editor, collections, statistics, deletion
    • Server-side route titles via req.t()
    • Full site coverage using i18next-parser output shows zero unextracted keys

Coding Phase 7 β€” Edge Cases and Testing (July 31– August 6): ~20h

  • Pluralization (t(β€˜key’, {count: n})), date/number formatting
  • Pseudo-localization via npx pseudolocale β€” 40% longer strings for 100% UI coverage without real translations
  • DOM overflow scan (scrollWidth > clientWidth) and (scrollheight >clientheight) run across all main routes
  • French + German seed translations β€” full UI test in both languages
  • CSS stress-test with long German words

Coding Phase 8 β€” Final Polish (August 7 – August 16): ~20h

  • Zero-key sweep with GitHub Action
  • Buffer for missed items
  • Documentation, final report, demo video

Extended / Future Goals:

Translation completeness threshold: Only activate a language once it crosses a minimum coverage percentage , users never see a half-translated UI unless they explicitly choose an incomplete language

RTL language support (Arabic, Hebrew): Switch direction: rtl via i18next language metadata automatically.

Complex pluralization: Russian, Polish, Arabic have 3+ plural forms β€” i18next’s CLDR resolver handles them automatically.

Apply to ListenBrainz website: The infrastructure built here is a direct blueprint.



Detailed information about yourself:

What type of music do you listen to?

I like soft, indie, and chill music that keeps my mind focused while building. Some of my favourites:

What type of books do you read?

I mostly read tech and self-improvement books. Currently reading curtains .
What aspects of the project you’re applying for interest you the most?

The SSR hydration challenge interests me the most , making sure the server and client use identical locale and translation data so the page never flickers or mismatches. I hit this exact problem during my prototype build and had to trace the full generateProps() β†’ renderToString() β†’ hydrate() pipeline to understand where i18n state needed to live. That kind of problem, where you have to deeply understand the architecture before you can solve it cleanly, is what I enjoy most.

Have you ever used MusicBrainz Picard or any of our projects?

I use BookBrainz to look up editions and series while working on the codebase, and I contribute to ListenBrainz. I have not used Picard yet.

When did you first start programming?

I started programming in 2022. I was always fascinated by computers and that curiosity is what pulled me into software development.

Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?

My primary open source contributions are to the MetaBrainz ecosystem (links in Why Me above).

Tell us about the computer(s) you have available for working on your SoC project!

Laptop: AMD Ryzen 5 7535HS with Radeon Graphics (3.30 GHz), 8 GB RAM, 64-bit Windows 11

How much time do you have available, and how would you plan to use it?

I have no other commitments during the summer and can dedicate 20–25 hours per week. The timeline above is planned around this, with buffer built into Phases 7 and 8.


References:

1 Like

hey @mr_monkey , i guess you are a bit busy nowadays so i worked on my proposal in the meantime ,can u please have a look at this and give your opinion. as i tried my best

@garvthakre
Thank you for submitting your proposal. I’ll soon share a detailed review of the proposal.

Before that, I wanted to ask: did you use any AI tools while preparing your proposal? If so, could you briefly describe how they were used?

@anshgoyal31 , yes i used ai to improve its structure and presentation , but all research and work is done by me , i am constantly updating it , beacuse my prototype is also ready , and i’ll be happy to answer technical questions .

I read your proposal in depth, and I have a few questions which would help me understand things better.

When the user first lands on the site with no cookie, how does the server decide which language to render?

We could either put locale files under src/client/i18n/locales and bundle them with webpack, or put them in public/locales and load them from the server. Why do you feel the latter approach would be better for BookBrainz?

In your middleware you only use getAcceptedLanguageCodes(req) to choose the language. But when the user picks French from the dropdown you set a cookie called bb_lang and reload. On that next request, how does the server know to use French?

How did you calculate these? And if halfway through we find there are a lot more strings, how would you decide what has to be translated first and what can be left for later?

Can you elaborate on how you would run the CSS stress-test?

I believe you should setup unit tests in the starting of the project

.

Your GitHub Action commits updated English JSON on every push to master, and Weblate would push translation updates to the same repo. How would you avoid merge conflicts or one overwriting the other?

@anshgoyal31 ,thanks for the review .
so l’ll answer point wise of every questions and suggestions .
1.the middleware reads the accept-language header that browser will send , the getAcceptedLanguageCodes (it return a ranked list like [β€˜en’, β€˜fr’] and the middleware picks the first supported local ) . no cookie needed in the first visit . i also merged a small PR already in the codebase related to this .
2.The reason to not use webpack is simple , because if the translations were inside the translation pack then after every contribution in translation it would require a full rebuild and redeploy and also the user needs to download the whole pack again. I dont think that’s a very feasible option , with /public/locales the server reads the file at runtime, maintainer reads and merges the weblate PR and translation is live .
3.thanks for the getAcceptedLanguageCodes(req) suggestion , yes i will fix the middleware to first checks req.cookies?.bb_lang , uses it if its a supported locale , and falls back to the accept-language header if there’s no cookie.
So the flow will be : user picks french => cookie is set => page reloads => server reads cookie => renders in french. (updated this one too ).
4.i use global search feature in VS code and search for terms like button labels,modal titles, etc , and it give me a rough idea of the counts, and for the prioritization i will start with nav and footer (every user sees them) and then entity pages , then entity editor , then search and collections , then admin and error pages .
5.the plan is to seed public/locales/de/ with german translations and then run it locally and visually inspect the layout with the translation and check for horizontal overflow and containers being pushed out of their bounds, also add overflow-wrap: break-word or hyphens:auto to the css.
6. agreed ,i’ll move the unit tests to the top , thanks for this suggestion(already done put this in phase 1 at step 3 ) .
7.No, there will be no conflicts because my and any other contributor action only writes to the β€˜/locales/en’ (english json files) and the weblate only writes to the other language json like β€˜/locales/fr’,β€˜/locales/hi’ etc.

hello @anshgoyal31 , i have a question about the language selector that i am thinking that do we really need dropdown because as the language list started to grow it looks odd or cluttered on the navbar , or should i give this dropdown in the profile or setting page instead ? i think that’s a more practical place for it . would loved to know your and @mr_monkey opinion on this .

A few followup questions:

That makes sense in general, but in our setup we build a Docker image where both src/ and public/ are copied into the image at build time. So how would public/locales get updated at runtime without rebuilding and redeploying the image?

Still curious, how did you map specific strings to each area (like Entity Editor vs Search) using global search? How did you avoid double counting or missing strings? Because this can impact timelines.

How would you ensure coverage across the entire UI? Visual inspection can miss cases - would you use any systematic or automated approach?

well currently in the setup only src/ is included and public/ is not there because there is nothing inside it but don’t worry i already fixed this too and using it in my prototype .

In the docker setup too public/locales will win because docker rebuilds only the public/locales and yarn install and yarn build layers are cached and skipped . but for the webpack it will invalidate the entire yarn build layer every time. and for adding a new language it will need to update imports and config too every time for every new language but for public/locales there is no code changes we just need to create the folders like locales/hi/ and it will dynamically read the folders exist in public/locales and done .

so as i noticed that the public/ is not inside the dockerfile so i thought that keeping it outside the Docker will be more beneficial due to its runtime update ,and yes inside the docker it will need to rebuild but the rebuild will be lightweight since it only copies JSON files .

i mapped the strings to areas by directory structure like files in src/client/entity-editor is noted as Entity Editor namespace,src/client/component/pages/ is noted as static pages namespace etc . The global search was used to get rough counts per folder, not a precise scan .
For the actual implementation, double counting is not an issue because i18next-parser handles it , it will scan every file , extracts every t('key') call and deduplicates automatically and for missing strings i have buffer time in the phase 8 of timeline and one more thing since my timeline is structured around pages/components rather than string counts, even if I’ve under-counted strings in an area, the time allocated for that page/component covers them.

beyond visual inspection , i will use psuedo-localisation , it will generate 40% longer strings for every key using the pseudolocale package which gives 100% coverage of all migrated strings without real translations and then a DOM overflow scan with a browser console script that will check scrollwidth > clientwidth and scrollheight > clientheight and flags every overflowing element ,i’ll run this script on every main route to ensure full UI coverage . so in this way it will solve the issue without waiting for actual translations .

i also updated the timeline to be more detailed and routes specific and also add the pseudo-loaclisation in the phase 7 .