You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The i18n setup (i18next + HttpBackend + LanguageDetector, namespaces common/settings/chores) is in place, but only a handful of components actually call t() — most of the UI renders hardcoded English. As a result, selecting a language today localizes only a small part of the app: login, the chore list, the chore editor, and most settings still render in English regardless of the chosen language.
I've been adding a Russian locale (#143) and, in the process, mapped the gap: the large majority of user-facing strings live outside i18next. I'd like to help close that gap and contribute it back incrementally.
Goal
Make the whole user-facing UI translatable through the existing i18next setup, so a locale actually localizes the app — without any framework change or architectural churn.
Approach (feedback very welcome before I go wide)
Feature-scoped namespaces: keep common/chores/settings, add auth, history, things, points, filters, notifications as needed. Generic strings stay in common and are reused.
English stays the source: each string moves into en/<ns>.json; the component keeps only t('ns:key'). Language-agnostic — other locales then just copy-and-translate.
Pluralization done right: replace hardcoded English plural logic (${n === 1 ? 'task' : 'tasks'}) with i18next count forms via Intl.PluralRules (this also addresses the hardcoded-plural issue I'm filing separately).
Small, reviewable PRs: one namespace / cohesive feature area per PR, referencing this issue, in priority order (daily screens first). No mega-diffs.
Status
Work has started on my fork. I'll open PRs feature-by-feature and reference this issue so coverage lands in reviewable pieces. I'll aim to take this as far as I can toward the app being genuinely multilingual.
Does this approach (feature-scoped namespaces, English-as-source, incremental PRs) work for you? Any preferences on namespace naming or ordering before I start submitting?
Status update — 3 Aug 2026 (audited against develop @ 412faf4)
A lot landed on develop these last two weeks — the modal/button unification, unified empty states, the project-management rework (#186), the onboarding reshape (#185). My fork branched before all of it, so rather than send PRs and discover the collisions during review, I re-audited every file first. Everything below is derived from that audit, and the ordering it implies is the ordering I'd like to follow.
129 components on my fork call t() today. Measured against current develop:
51 are untouched by develop and by every open branch — these can land as-is and will not need a rebase.
2 are clean on develop but sit in an open branch (ActivitesCard.jsx, SubTask.jsx) — leaving them alone.
75 need re-extraction on top of your refactors before they're worth reviewing.
Each PR below contains only files develop hasn't touched, so none of them should ever need a rebase. The first three extend namespaces that already exist, which means no src/i18n/config.js change and therefore no conflicts between my own PRs either.
settings — 16 files. Extends the existing namespace. Lowest-risk of the new ones.
common — 14 files. Shared inputs, calendar, nav, loading, error states.
chores — 13 files. List controls, filter builder, repeat/trigger sections. The four picker fields that would otherwise appear here are in i18n: extract the task-form picker fields (Part of #145) #155 instead, so the two never touch the same file.
New namespaces — history, points, projects (1 file each). These do touch config.js, one line apiece.
Then, as your refactors settle, the blocked zones in the same shape — largest first (Modals, then Chores, then Authorization).
Namespace and key conventions
This is the part I'd most like you to push back on, because it's cheap to change now and expensive after twenty PRs. What I've been doing:
Namespaces are feature-scoped and lowercase, extending yours: common, settings, chores, plus auth, history, points, projects, things, filters, labels, timer as each zone lands. Anything reused across zones goes to common rather than being duplicated.
Keys are camelCase, nested one level by section, e.g. settings:circleSettings.copyCode. Generic actions stay flat in common (common:save, common:cancel).
Interpolation uses named variables — {{name}}, {{count}} — never positional or concatenated fragments.
Plurals use i18next suffixes on the English source (reminder_one / reminder_other) with t(key, { count }), replacing the hardcoded n === 1 ? 'task' : 'tasks' pattern. Other locales then add whatever forms their language needs without touching the component.
English is the only value shipped in these PRs. No translations ride along, so every PR is a pure refactor.
<Trans> is avoided. Where a sentence contains a link, the string is split into separate keys rather than embedding markup in the dictionary.
If you'd rather have flat keys, different namespace boundaries, or everything in common, say so and I'll rework the fork to match before sending more.
Out of scope
MFAVerificationModal — entangled with the MFA flow logic; deserves its own pass rather than being folded into a mechanical extraction.
The Go backend (donetick/donetick) — server-side strings are a separate problem and I'm not touching them.
Anything you'd rather keep hardcoded — tell me and I'll skip it.
Claiming a zone
To avoid a repeat of the collision on the auth screens: if you or anyone else is about to work in one of the zones above, drop a comment here and I'll mark it taken and route around it. I'll do the same before I start each PR.
Open PRs affected by this audit
i18n: extract authorization screen strings (Part of #145) #168 (auth) — extracted against the pre-revamp login screens, so it no longer applies; the zone was rewritten twice and Reshape onboarding v1.3 #185 is still in flight. I'd rather not re-extract into a moving target, so I'm marking it blocked instead of force-pushing something that may need doing a third time. Happy to close it if you prefer a clean slate.
i18n: extract the task-form picker fields (Part of #145) #155 (task creation) — I audited this one too. Six of its twelve files were rewritten under you: DueDatePickerField lost 522 lines to Enhance project management UI and handle bulk changes #186, AddTaskModal gained 233, and RepeatPickerField / NotificationPickerField went through the modal unification. That's about 70% of the diff, which is why GitHub has been showing it as conflicting. The other six files — the task-form pickers — are untouched. I'd rather not leave a red PR sitting in your queue, so unless you object I'll force-push it down to just those (minus SubTask.jsx, which is clean on develop but sits in an open branch) and retitle it accordingly. The rewritten half goes back in the queue behind the modal work.
Background
The i18n setup (i18next + HttpBackend + LanguageDetector, namespaces
common/settings/chores) is in place, but only a handful of components actually callt()— most of the UI renders hardcoded English. As a result, selecting a language today localizes only a small part of the app: login, the chore list, the chore editor, and most settings still render in English regardless of the chosen language.I've been adding a Russian locale (#143) and, in the process, mapped the gap: the large majority of user-facing strings live outside i18next. I'd like to help close that gap and contribute it back incrementally.
Goal
Make the whole user-facing UI translatable through the existing i18next setup, so a locale actually localizes the app — without any framework change or architectural churn.
Approach (feedback very welcome before I go wide)
common/chores/settings, addauth,history,things,points,filters,notificationsas needed. Generic strings stay incommonand are reused.en/<ns>.json; the component keeps onlyt('ns:key'). Language-agnostic — other locales then just copy-and-translate.${n === 1 ? 'task' : 'tasks'}) with i18nextcountforms viaIntl.PluralRules(this also addresses the hardcoded-plural issue I'm filing separately).Status
Work has started on my fork. I'll open PRs feature-by-feature and reference this issue so coverage lands in reviewable pieces. I'll aim to take this as far as I can toward the app being genuinely multilingual.
Does this approach (feature-scoped namespaces, English-as-source, incremental PRs) work for you? Any preferences on namespace naming or ordering before I start submitting?
Status update — 3 Aug 2026 (audited against
develop@412faf4)A lot landed on
developthese last two weeks — the modal/button unification, unified empty states, the project-management rework (#186), the onboarding reshape (#185). My fork branched before all of it, so rather than send PRs and discover the collisions during review, I re-audited every file first. Everything below is derived from that audit, and the ordering it implies is the ordering I'd like to follow.129 components on my fork call
t()today. Measured against currentdevelop:developand by every open branch — these can land as-is and will not need a rebase.developbut sit in an open branch (ActivitesCard.jsx,SubTask.jsx) — leaving them alone.Coverage map
t()on forkviews/components)Proposed order
Each PR below contains only files
develophasn't touched, so none of them should ever need a rebase. The first three extend namespaces that already exist, which means nosrc/i18n/config.jschange and therefore no conflicts between my own PRs either.develophasn't touched so it stops being a red PR (details at the bottom).settings— 16 files. Extends the existing namespace. Lowest-risk of the new ones.common— 14 files. Shared inputs, calendar, nav, loading, error states.chores— 13 files. List controls, filter builder, repeat/trigger sections. The four picker fields that would otherwise appear here are in i18n: extract the task-form picker fields (Part of #145) #155 instead, so the two never touch the same file.history,points,projects(1 file each). These do touchconfig.js, one line apiece.Then, as your refactors settle, the blocked zones in the same shape — largest first (Modals, then Chores, then Authorization).
Namespace and key conventions
This is the part I'd most like you to push back on, because it's cheap to change now and expensive after twenty PRs. What I've been doing:
common,settings,chores, plusauth,history,points,projects,things,filters,labels,timeras each zone lands. Anything reused across zones goes tocommonrather than being duplicated.settings:circleSettings.copyCode. Generic actions stay flat incommon(common:save,common:cancel).{{name}},{{count}}— never positional or concatenated fragments.reminder_one/reminder_other) witht(key, { count }), replacing the hardcodedn === 1 ? 'task' : 'tasks'pattern. Other locales then add whatever forms their language needs without touching the component.<Trans>is avoided. Where a sentence contains a link, the string is split into separate keys rather than embedding markup in the dictionary.If you'd rather have flat keys, different namespace boundaries, or everything in
common, say so and I'll rework the fork to match before sending more.Out of scope
MFAVerificationModal— entangled with the MFA flow logic; deserves its own pass rather than being folded into a mechanical extraction.donetick/donetick) — server-side strings are a separate problem and I'm not touching them.Claiming a zone
To avoid a repeat of the collision on the auth screens: if you or anyone else is about to work in one of the zones above, drop a comment here and I'll mark it taken and route around it. I'll do the same before I start each PR.
Open PRs affected by this audit
DueDatePickerFieldlost 522 lines to Enhance project management UI and handle bulk changes #186,AddTaskModalgained 233, andRepeatPickerField/NotificationPickerFieldwent through the modal unification. That's about 70% of the diff, which is why GitHub has been showing it as conflicting. The other six files — the task-form pickers — are untouched. I'd rather not leave a red PR sitting in your queue, so unless you object I'll force-push it down to just those (minusSubTask.jsx, which is clean ondevelopbut sits in an open branch) and retitle it accordingly. The rewritten half goes back in the queue behind the modal work.