fix(i18n): use singular customer key in transaction modal - #482
Open
sridharkalaibala wants to merge 784 commits into
Open
fix(i18n): use singular customer key in transaction modal#482sridharkalaibala wants to merge 784 commits into
sridharkalaibala wants to merge 784 commits into
Conversation
The guard called countExistingBranches unconditionally, so a repository that does not implement it - an older one, or a test double - threw a TypeError before the catch could apply, and every install failed. Nine tests said so and I pushed before reading them. An unanswerable question is treated as empty, deliberately: refusing then would block genuine signups, which is a worse failure than the one being prevented.
Bumps the api-patch-and-minor group with 16 updates in the /api directory: | Package | From | To | | --- | --- | --- | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1111.0` | `3.1121.0` | | [axios](https://github.com/axios/axios) | `1.19.0` | `1.20.0` | | [html-to-text](https://github.com/html-to-text/node-html-to-text) | `10.0.0` | `10.0.1` | | [joi](https://github.com/hapijs/joi) | `18.2.3` | `18.2.5` | | [mongodb](https://github.com/mongodb/node-mongodb-native) | `7.5.0` | `7.6.0` | | [mongoose](https://github.com/Automattic/mongoose) | `9.9.2` | `9.9.4` | | [morgan](https://github.com/expressjs/morgan) | `1.11.0` | `1.12.0` | | [multer](https://github.com/expressjs/multer) | `2.2.0` | `2.3.0` | | [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.5` | `9.0.6` | | [pdfkit](https://github.com/foliojs/pdfkit) | `0.19.1` | `0.20.1` | | [unzipper](https://github.com/ZJONSSON/node-unzipper) | `0.10.14` | `0.12.5` | | [uuid](https://github.com/uuidjs/uuid) | `14.0.1` | `14.0.2` | | [@redocly/cli](https://github.com/Redocly/redocly-cli) | `2.46.1` | `2.49.0` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.2.0` | `26.4.0` | | [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.9.1` | | [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.4.2` | `30.5.0` | Updates `@aws-sdk/client-s3` from 3.1111.0 to 3.1121.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1121.0/clients/client-s3) Updates `axios` from 1.19.0 to 1.20.0 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](axios/axios@v1.19.0...v1.20.0) Updates `html-to-text` from 10.0.0 to 10.0.1 - [Changelog](https://github.com/html-to-text/node-html-to-text/blob/master/CHANGELOG.md) - [Commits](html-to-text/node-html-to-text@10.0.0...10.0.1) Updates `joi` from 18.2.3 to 18.2.5 - [Commits](hapijs/joi@v18.2.3...v18.2.5) Updates `mongodb` from 7.5.0 to 7.6.0 - [Release notes](https://github.com/mongodb/node-mongodb-native/releases) - [Changelog](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md) - [Commits](mongodb/node-mongodb-native@v7.5.0...v7.6.0) Updates `mongoose` from 9.9.2 to 9.9.4 - [Release notes](https://github.com/Automattic/mongoose/releases) - [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md) - [Commits](Automattic/mongoose@9.9.2...9.9.4) Updates `morgan` from 1.11.0 to 1.12.0 - [Release notes](https://github.com/expressjs/morgan/releases) - [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md) - [Commits](expressjs/morgan@1.11.0...1.12.0) Updates `multer` from 2.2.0 to 2.3.0 - [Release notes](https://github.com/expressjs/multer/releases) - [Changelog](https://github.com/expressjs/multer/blob/main/CHANGELOG.md) - [Commits](expressjs/multer@v2.2.0...v2.3.0) Updates `nodemailer` from 9.0.5 to 9.0.6 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](nodemailer/nodemailer@v9.0.5...v9.0.6) Updates `pdfkit` from 0.19.1 to 0.20.1 - [Release notes](https://github.com/foliojs/pdfkit/releases) - [Changelog](https://github.com/foliojs/pdfkit/blob/master/CHANGELOG.md) - [Commits](foliojs/pdfkit@v0.19.1...v0.20.1) Updates `unzipper` from 0.10.14 to 0.12.5 - [Release notes](https://github.com/ZJONSSON/node-unzipper/releases) - [Commits](https://github.com/ZJONSSON/node-unzipper/commits) Updates `uuid` from 14.0.1 to 14.0.2 - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md) - [Commits](uuidjs/uuid@v14.0.1...v14.0.2) Updates `@redocly/cli` from 2.46.1 to 2.49.0 - [Release notes](https://github.com/Redocly/redocly-cli/releases) - [Commits](https://github.com/Redocly/redocly-cli/compare/@redocly/cli@2.46.1...@redocly/cli@2.49.0) Updates `@types/node` from 26.2.0 to 26.4.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `eslint` from 10.8.1 to 10.9.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.8.1...v10.9.1) Updates `jest` from 30.4.2 to 30.5.0 - [Release notes](https://github.com/jestjs/jest/releases) - [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jestjs/jest/commits/v30.5.0/packages/jest) --- updated-dependencies: - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1121.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: axios dependency-version: 1.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: html-to-text dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: api-patch-and-minor - dependency-name: joi dependency-version: 18.2.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: api-patch-and-minor - dependency-name: mongodb dependency-version: 7.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: mongoose dependency-version: 9.9.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: api-patch-and-minor - dependency-name: morgan dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: multer dependency-version: 2.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: nodemailer dependency-version: 9.0.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: api-patch-and-minor - dependency-name: pdfkit dependency-version: 0.20.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: unzipper dependency-version: 0.12.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: uuid dependency-version: 14.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: api-patch-and-minor - dependency-name: "@redocly/cli" dependency-version: 2.49.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: "@types/node" dependency-version: 26.4.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: eslint dependency-version: 10.9.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: api-patch-and-minor - dependency-name: jest dependency-version: 30.5.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: api-patch-and-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Nobody could answer "how much of the app is translated?". The build falls back to the English already inside the <lang> tag when a key is missing, which is right - it is why a half-translated language is usable rather than broken - but it is also invisible. Tamil has been 88% translated for months and reads as finished. Three numbers, none of which were known before: the UI uses 648 DISTINCT keys, not the 1,408 <lang> tags - a key on six screens is one string to translate; Tamil answers 573 of them, so 75 screens' worth of words are showing English to somebody who asked for Tamil; and ta.json carries 313 entries nothing references - 35% of the file - so handing a translator "the whole file" asks for a third more work than the product has words in it. An empty string counts as untranslated, or generating a skeleton would report a language as finished. --min gates a release without failing ordinary runs; --json is for CI; --missing <lang> prints the list to hand to a translator. Background and the path this serves: Intranet docs/MULTI_LANGUAGE_ARCHITECTURE.md
Owner: "okay start with tamil."
The app decided its language by comparing a page FILENAME - sixty-three copies
of
PosnicPro.local.get('language_herf') === 'ta_dashboard.html'
across fifteen files, each a two-way branch with the Tamil written inline
beside the English. A third language did not fit anywhere in that, which is why
there has never been one. This is stage L1 of Intranet
docs/MULTI_LANGUAGE_ARCHITECTURE.md and changes no behaviour.
PosnicPro.i18n now holds a language CODE, derived from the old filename so
existing Tamil shops are migrated rather than quietly reset to English on
upgrade, and t(key, english) answers with the translation or the English it was
handed. That fallback is the whole safety property: before the pack loads, if
it 404s, if a key is missing or a language is half done, a real English word
ships - never "undefined", which the HTML build did for months.
63 branches became 82 t() calls and 2 branches. The two left are not text:
Tamil needs its own type sizes on the sale screen and dashboard.
WHAT THIS TURNED UP. sales.js held nine Tamil strings as mojibake - UTF-8 bytes
saved as cp1252 - so the most used screen in the product had been showing
Tamil shopkeepers "பà¯à®¤à®¿à®¯" where it meant "புதிய". Invisible in review,
unmistakable to the person using it. Moving the words to ta.json fixed them,
and the build now refuses a pack containing mojibake so it cannot return.
ta.json goes from 894 keys to 582 by dropping every entry nothing references -
a third of the file. The pack shrinks 71KB to 43KB, and a translator is now
asked for the 657 strings the product actually has rather than a third more.
The words are fetched at runtime from languages/<code>.json. English fetches
nothing at all, because it is in the markup and in every t() call.
Not retranslated, deliberately: two forms of "Edit" are kept because the sale
screen says திருத்த and every other screen says திருத்தப்பட்ட. Changing what a
shopkeeper reads is a decision for someone who speaks Tamil.
Tamil is 89% complete, and it always was - 75 keys have been falling back to
English silently. tests/tools/i18n-coverage.js now counts t() calls as well as
HTML, so that number is visible rather than discovered.
The build translated at build time, which meant writing a COMPLETE SECOND COPY
of every page for each language: 2.3MB of duplicated markup to deliver 43KB of
Tamil, and roughly 23MB by the tenth language before anyone had typed a word of
the eleventh.
Output is now 2.3MB whatever happens - one tree, and adding a language costs
the 43KB of its pack. That is the whole point of the exercise, and it is the
number the owner asked to hold down.
Pages keep their keys and their English:
<lang class="lang_item_name">Item name</lang>
and PosnicPro.i18n.apply() swaps the words in at load. English is therefore not
"the default language" but the text physically in the file: the app renders
correctly with no dictionary, offline, on first run, and if every fetch fails.
TWO PLACES THE TAG CANNOT LIVE, and both were checked rather than assumed.
Inside <title> and <option> the parser builds no element for it, so a title
would have shown the customer literal angle brackets in their tab. There the
key is hoisted onto the parent as data-t. A survey of all 1,408 tags found
exactly 4 titles and 53 options and nothing in a textarea or an attribute.
1,351 places that held bare text now hold an inline element around it, so
lang { display: contents } removes its box entirely - spacing is unchanged on
screens nobody is going to re-check by hand. No CSS matched `lang` or any
`.lang_*` class before this, which was also checked rather than assumed.
Switching language no longer navigates. It used to load ta_dashboard.html and
lose whatever was on screen; there is one page now, so it fetches the words and
redraws in place.
The build also sweeps per-language pages an older build left behind. A stale
one still loads and still looks right, frozen at whatever the app said that
day, which is worse than a page that is simply missing.
Found while doing this: the earlier codemod only looked at modules/js, so
PosnicPro.js had quietly kept three filename comparisons and three Tamil words
of its own. The tests now scan the whole script tree, because a test scoped
more narrowly than the problem finds nothing and says so confidently.
Runtime behaviour is tested against a real DOM rather than by matching source
text - the change is about what the page looks like after load.
Pages ship in English and the words arrive by fetch, which is what made the output stop multiplying - but it also means a Tamil shop that loses its connection silently reverts to English, on a till, in front of a customer. The worker now caches the pack on FIRST USE, with the same stale-while-revalidate treatment the reference data already gets: answered instantly from cache, refreshed behind the answer, swept by the versioned cache name on release. Cached on first use rather than precached, deliberately. Precaching every language would put back exactly the weight this work removed - a shop should carry the one language it chose, so the tenth language costs the other nine shops nothing. DELIVERY NEEDED NO CODE. The owner suggested keeping packs in S3 and downloading on demand. The asset channel already ships frontend/public signed, staged and auto-reverting, and the packs are written into that tree, so they are already signed and delivered by construction - no second, weaker door. A test pins that the bundler still walks the whole tree rather than a list that could quietly forget them. One of my own test expectations was wrong and is worth recording: I expected /uploads/languages/x.json to match the pack pattern. It does not, and should not - "x" is not a language code and that path is customer uploads. The pattern was right; the expectation was not.
The last place a new language still meant editing code was the menu itself: header.html carried one hand-written <a> per language. It is now built from languages/index.json, which the build publishes from the language list, so adding Hindi is languages/hi.json plus one line of config and nothing else. One English entry stays in the markup deliberately. If the list cannot be read the menu must still offer something - losing the OTHER languages is a bug, leaving a shop with no way to choose at all is a worse one. The click handler is delegated, because entries that did not exist when the page loaded would otherwise be dead. The list carries display names written in the language itself. Somebody looking for Tamil is looking for "தமிழ்", not for "Tamil". Proved rather than asserted: I added a language end to end - published, offered in the menu, under its own name - and the only thing it dirtied was its own JSON file. No HTML, no module JS, no build code. Then put it back and checked the tree was as it started. Also fixed while here: buildCss looped the languages and wrote the SAME path each time, because the filename never carried the language. Every stylesheet was rewritten once per language for no effect - harmless at two, and pointless work that grows with every language added. L1-L5 of Intranet docs/MULTI_LANGUAGE_ARCHITECTURE.md are now done. Output is 2.3MB whatever happens, and a language costs the 43KB of its pack.
--missing prints key names, which is the wrong thing to hand a person: nobody can translate "lang_conversion_factor_title" without being told it says "Conversion factor" and sits on the item screen. --worksheet ta writes a JSON file with the English and the screen beside every blank. --merge ta takes it back, sorted, without the duplicate keys and lost ordering that hand-merging produces. Blank entries are skipped rather than written. An empty string counts as untranslated everywhere else in this system, so writing one would report the language as finished while showing the customer nothing. A merged value that looks like mojibake is refused outright, with the advice to save as UTF-8. That is the exact failure that had been live on the sale screen for who knows how long, and a worksheet round-tripped through the wrong editor is precisely how it would come back. The round trip was tested end to end - worksheet out, two strings filled in, merged, coverage moved 582 to 584 - and then reverted.
A <lang> tag broken over three lines in the source gave back the newlines and
the indentation with it, so the worksheet showed
"Your shop was set up with one
trade's sample products. Pick a different"
which is hard to read and would have been translated with the whitespace baked
into the result. The sentence is what matters; the markup's shape is not.
--worksheet writes a file on its way to a person and back. Half-filled, it is not the translation and not the language file; the finished words belong in frontend/languages/<code>.json after --merge.
Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com>
Owner, who speaks Tamil, on the two forms I had deliberately kept apart:
திருத்த -> For Edit
திருத்தப்பட்ட -> Edted pastence
So every screen except the sale screen has been labelling its Edit button
"Edited" - a past participle on a control that has not done anything yet. The
sale screen was the only one that had it right, which is why the two forms
existed at all.
Both keys collapse onto திருத்த, and lang_action_edit_sale is gone: there is
one word for Edit now because there is one word for Edit.
This is exactly the class of mistake I said I would not make on my own. Moving
the words out of the code is a refactor; deciding what a shopkeeper reads is
not, and it took one line from someone who speaks the language to settle
something no amount of reading the source could.
Correcting "Edit" turned up the useful question: if a past participle sat on a
button for months, what else is wrong? I cannot answer that - I do not speak
Tamil - but two kinds of mistake are mechanical and do not need me to.
--review <lang> reports:
the same English rendered two different ways, so the interface calls one
thing by two names;
the same translation used for two different English meanings, which means at
least one of them is the wrong word.
Sixty-one on Tamil, and the first pass already shows real ones: "Item position"
is labelled with the words for "branch access", "Add fields" with the words for
"default user", and "Apply" shares its translation with "Search".
The searching is mechanical; the judgement stays with the person who speaks the
language. That is the split worth keeping - hundreds of strings narrowed to a
short list, rather than an opinion I am not qualified to have.
Also fixed here: --json stringified a Map and a Set, so `context` arrived as {}
and every consumer saw no English at all. It looked like it worked.
Owner: contributors are interested, and translations should be a first task for newcomers - for every language, not just Tamil. Language files move from frontend/languages/ to languages/ at the repository root. They are a contribution surface before they are a build input: somebody who speaks Kannada and has never opened this project should find them by looking at the repository, not by knowing which build step reads them. --new <code> starts a language that does not exist yet. The FIRST person to offer Hindi previously had nothing to begin with: --worksheet needs a file to compare against, so the tool could say how incomplete a language was but not how to start one. --merge now creates the file, so a language begins existing the moment somebody translates two strings. Kept deliberately separate from --worksheet: a typo would otherwise generate a worksheet for a language nobody speaks, and the first sign of it would be a pull request adding languages/hj.json. docs/TRANSLATING.md is the whole job on one page, and every command in it was executed rather than written from memory. It says plainly that a half-finished language is a real contribution, because that is the thing that stops people starting. check-translations.js and a CI job answer the three questions a translation PR raises, in the time it takes to push. The encoding one is why this exists at all: mojibake is invisible to a reviewer who does not read the script, and nine Tamil strings reached the sale screen that way and stayed for months. Both Latin-1 and cp1252 are caught - an earlier detector of mine tested only the first and passed a visibly corrupt file. Every failure names the fix, points at the guide, and says that an unclear message is a bug in the check rather than in the contributor's work. Eleven tests break a language file on purpose and confirm each is caught, because a validator nobody has watched fail is indistinguishable from one that passes everything.
Signed-off-by: Ansari Ibrahim <ansarifahad.7577@gmail.com>
npm run dev -> http://localhost:3000
npm start opens Electron, which is right for the product and wrong for a
contributor: you cannot hit refresh, and you cannot look at a translation in
place without building an installer. That last one mattered - P1 asked people
to translate an interface they had no way of seeing.
Nothing here is a second implementation, and that is the useful discovery.
api/app.js already serves the built frontend at /public on the API's own
origin, which is exactly how the desktop app loads it: main.js opens
`${origin}/public/dashboard.html`. So the pieces were all there and nobody had
arranged them - this only does what main.js does.
Four things, each of which failed a clean clone before:
the API refuses to start without signing secrets, and rightly so - every
Posnic once shared the same string. These are generated once into api/.env
(gitignored) rather than per run, because regenerating them signs you out on
every restart;
the API serves files, not sources, so an unbuilt frontend is a 404 that
explains nothing;
mongod, started through the same manager main.js uses - it already handles
Electron being absent;
the API itself, on 3000.
Every failure names the fix. Somebody trying this for the first time should not
have to read the script to get past it.
Verified by running it, not by reading it: the login page, the Tamil pack and
the language manifest all answered 200, and the served HTML carries its <lang>
markers - so a translator can now switch language in the top bar and watch
their own words appear.
develop is now a real branch that contributor work lands on, so it needs the same gate main has. Pull requests already ran it; pushes to develop did not, which would have meant a merged change sitting untested until somebody opened the next PR.
Owner: "main only me need to merge. develop also as of now only me."
Both branches are protected, and verified by reading the rules back rather than
trusting the API's 200:
only sridharkalaibala may push or merge
a pull request with one approval, and stale approvals dropped on new commits
six CI checks, all of which run unconditionally - "Lint (reporting only)" is
deliberately not one of them, because it reports and does not gate
no force pushes, no branch deletion, review threads must be resolved
enforce_admins is off on purpose. With it on, the owner could not merge his own
release either, and the usual result is somebody turning the whole thing off in
a hurry. Contributors cannot merge regardless - nobody else has write access.
CI now runs on pushes to develop, not just main. Without that a merged change
would sit untested until the next pull request happened to run.
Seven labels for the state a change is in, named for the state rather than for
who should act: "ready for QA" says where the change is, where "needs tester"
would say what a volunteer owes, and volunteers owe nothing.
A merge to develop now labels its own pull request and comments where to try
it. Left to people to remember, that label gets set for a week and then never
again, and stops meaning anything. The workflow uses pull_request_target for
the write token and NEVER checks the pull request out, which is the thing that
makes that trigger dangerous.
Both templates and the quickstart now say to branch from develop, and the
quickstart explains what happens after a merge - that testing somebody else's
change needs no permissions at all.
Every push to develop goes to develop.posnic.io so anyone can try it without write access, a checkout or a build. THIS MACHINE RUNS UNREVIEWED CONTRIBUTOR CODE, and that one fact decides the whole design: it is NOT registered in the estate console, because a registered instance can be handed a real customer's shop by the provisioner and nothing afterwards would undo that; it gets its own database and its own secrets, generated on the machine; it never receives the control-plane, S3, payment or mail credentials - not by guarding them, but by simply not sending them, so there is nothing on that box that can reach anything real. The frontend builds in CI rather than on the box. A 2 GB machine running mongod and the API should not also run a gulp build, and building in CI means a broken build never reaches the sandbox at all. The last step asks the live URL for a 200 and fails the run if it does not get one. A deploy that reports success without checking is how a box sits broken for a week with a green badge above it. Off until DEVELOP_DEPLOY_ENABLED is set, because a workflow that fails on every push teaches people to ignore red marks. The machine itself needs AWS credentials this session does not have and should not have. Intranet docs/DEVELOP_SANDBOX_SETUP.md is the runbook, including the one thing that matters most: use a NEW key pair, because posnic-deploy opens production.
Owner: "however we need sync but you decide on that." The normal direction is develop -> main. main only moves ahead when something skipped that path: a hotfix applied directly, or the merge commit a release creates. Left alone, develop then silently lacks a fix that is already live, and the next contributor branches from a tree missing it. This opens a pull request instead of pushing. Both branches are restricted to the owner on purpose, and a workflow that force-pushed past that would make the protection a decoration - the rule would hold for people and not for automation, which is the wrong way round. It notices; the owner still decides. It merges rather than resets, because develop may carry work main has never seen and a reset would delete it silently. On a conflict it leaves the branch for a person rather than aborting, since aborting would leave develop quietly behind with nothing to show for it. It does nothing at all when develop is already current, which is the usual case. Also, said plainly in the contributor docs: develop.posnic.io is a PUBLIC sandbox running unreleased and untested code, holding demo data only, wiped, and readable by anyone. Somebody will otherwise type a real customer into it.
Owner: "once develop merged then i will do release. from develop to main." Promotion is a manual workflow that opens one reviewable develop -> main pull request, listing the commit subjects rather than hashes - somebody deciding whether to release wants to read what changed - and naming everyone whose work is in it, so the release notes are not the first time they see their name. Run by hand, never on a schedule. Deciding that develop has been tested enough is a judgement, and a timer would only ever guess at it. The pull request says plainly that merging it does not release anything: tagging main is what builds the installers, and that stays a separate deliberate act. docs/QA_PROCESS.md is written for somebody who wants to help and has no permissions, because that is who will read it. It opens by saying testing needs no write access and that reporting a break is as useful as fixing one, then shows a good QA comment and a better one - the better example is the failure, because a vague pass is worth less than a precise complaint. What is worth checking is a prompt rather than a checklist: does it do what it says, does the thing next to it still work, does it survive another language, does it work offline, does it survive a reload. Those are where this application actually breaks. Contributor credit needed nothing: release.yml already generates notes from merged pull requests, so names appear on their own.
The health check asked https://develop.posnic.io for a 200, which fails until a DNS record exists that nobody has added yet - so a perfectly good deploy would have gone red on somebody else's unfinished work. A job that reports on work it does not own is one people learn to ignore. It now checks the machine by IP, which is what the deploy actually changed, and reports the public hostname as a notice rather than requiring it.
The box was built by hand over SSH. A machine that exists only as somebody's shell history is one nobody can recreate after it breaks, so bootstrap, nginx config and the reset script now live in the repository. The banner is injected by nginx rather than built into the app. The application has no business carrying a warning it must never show a real shop - a flag for that is one bad default away from appearing on a customer's till - and this config exists only on the sandbox, so there is no path by which it reaches production. It sits at the bottom: the top of those screens is a working till, and covering it is how a banner becomes something people ignore. The nightly reset had to exist because the banner promises it. A promise the software does not keep is worse than no promise; somebody decides the box is safe for real data. --capture is the part that matters. Without a seed every tester walks the setup wizard before testing anything, and most will not bother. It refuses a dump under 2KB, because seeding from an empty database would wipe the sandbox every night while appearing to work - which was tested by trying it. Also on the box, verified after installing rather than assumed: robots.txt disallows everything, X-Robots-Tag says noindex, and nginx rate limits. A sandbox in search results is worse than no sandbox. One mistake worth recording: the first attempt patched the nginx config with sed and left duplicate directives. nginx refused it and kept serving the previous config, so the site never went down - which is the behaviour to rely on, not the edit to repeat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge part d of the Swahili worksheet: 426 keys covering the register session, receiving, quotes, roles and permissions, loyalty tiers, variants and the roster. Vocabulary follows the existing pack - sanduku la fedha for the till session, rejista for the terminal, ankara, nukuu ya bei, akiba. Brand names keep their own spelling rather than being transliterated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge the remaining German and Thai worksheets. Both packs now answer every key the interface asks for. Two labels the load-time revert had left in English for good: the ITEM / CUSTOMER / ACTION badge on a command-palette result, and the Movement dropdown's own label on Stock Activity once a movement is picked. Both sit in a top-level object literal, where a t() call runs before any pack exists, so they take the same shape as the report titles - the key travels with the English in the config, and the render, which runs long after the packs land, does the asking. The coverage scanner now reads a key as a word. It matched anything between the quotes of <lang class="...">, so a render site building that class from a variable was harvested as a key literally named "' + kindKey + '". Four such phantoms were already sitting in the English map; they are gone, and the pattern can no longer mint another. Also mark .husky/pre-commit executable. It has a shebang and the suite checks for that, so the hook was failing CI for everyone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge the last Swahili worksheet.
Three chips on the campaign editor insert a merge field into the message
template, and what they show is exactly what they insert. They were wrapped
in <lang>, so they went out to every translator and came back as
"{points} puntos", "Daraja {tier}", "{name} (jina)" - a chip that no longer
says what it does, in fifteen languages. The tags come off, the keys go, and
both sweep tools now read a lone {token} as data rather than prose, so the
next pass will not tag them again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge the Italian, Dutch and Sinhala worksheets. Italian and Dutch now answer every key the interface asks for; Sinhala reaches 84%. Six translators in a row reported the same fight and each lost it differently: "Provider Way2sms", "TextLocal", "Msimbo wa Pharmacode", "Stile Turbo C", a Razorpay tab prefixed with the Arabic for gateway. None of them was careless. The validator rejects a value identical to its English, which is right - that is what a skipped row looks like - but it had no exemption for a proper noun, so changing the name was the only way to pass. A payment tab that no longer names the payment provider. languages/_glossary.json now carries the names, every pack spells them the same way, and a test holds the line. Descriptive names are deliberately not on that list: Soft Dark and Diamond are words, and a theme picker in Thai should read in Thai. The two brands whose English was misspelled take the casing their companies use. The user permission grid says Read/Write and Read/Delete. It said R/Write, the R abbreviating Read, and four translators read that as Receiving or Return - the three packs that guessed wrong now match their own word for Read. The table sits in .table-responsive; the two saved characters were never worth the ambiguity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge part f for Arabic, Spanish, French, Hindi and Indonesian, and fill Tamil's last four keys. Three panels a shopkeeper reads were built by an inline <script> inside a page, which is a seam neither sweep covered: the HTML tagger strips <script> before it looks, and the JS tagger only walks static/script/js. The desktop card under the sign-in form, the boot watchdog shown when the till will not load, and the desktop tools button were all English in every language. They carry keys now, and the gaps report reads what an inline script builds rather than throwing it away. With that, and with an onclick handler no longer misread as prose - it holds a > that the text scan took for the end of a tag - the count of English no pack can reach is zero. The guard says zero instead of "at most two". tests/tools/i18n-screen.js answers the question the owner actually asked. Coverage says how much of a language is done; this says which SCREEN is still English, worst first, so an hour spent on a language can be spent where it shows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
E-invoice validation module, GST return gap analysis, and the government registration routes
A <lang> tag says two things at once: which words to show in English, and which key every other language is looked up by. Give two different labels the same key and English stays right on both screens while every other language is wrong on one of them - invisibly, because the person who can see it is not the person reviewing the diff. The branch form was the worst of it. City carried the key for "Supplier list", State the key for "Customers", Country the key for "Address". Read in English the form is correct. Nineteen columns labelled Tax carried the key for "Denomination Field", so a French till read "Champ de coupures" over its tax column and a Spanish one "Campo de denominación". Seventy-eight keys carried more than one meaning. languages/_english.json decides which meaning keeps the key, because that file is what every translator was shown, so it is what the packs already say - a majority vote would have been wrong thirty-one times, Tax among them. A hundred and forty-nine labels moved to a key that already means what they say and needed no new words; sixty-three took a new key, and those are translated here. Punctuation counts: lang_name means "Name:" and lang_name_title means "Name". Sixty-three tooltips were invisible to the tooling. The coverage scan looked for a whole tag and then the data-t-title inside it, but a module builds its buttons from three concatenated strings, so there is no closing bracket to find. Edit this branch, Delete this category, Print bill, Show on kiosk - none of them was in any pack, no worksheet ever offered them, and coverage said a hundred per cent. The scan anchors on the key now. Three tests hold the ground: no key carries two meanings, every key written into a page is one the sweep can see, and the ones already there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It reported the same seventy-five sites on every run and offered to fix them by wrapping each one in t(). All seventy-five were already correct. They are config data written at the top of a module, where a t() call runs before any pack exists - the failure that took the dashboard down - so they carry the key beside the English instead and the render site does the asking. A tool that always cries wolf teaches people to ignore it, and running --write on this one reintroduces the outage. It knows that shape now, so its count means something, and a test holds it at zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fifty-three glossary rows were blank - Sinhala missing forty of them, Tamil thirty. The words were already in those packs, under a key whose English is the term itself, so the rows are filled from the packs rather than translated again. Twenty-seven Tamil rows are still blank on purpose. Tamil renders the same English several ways - Add is three different words across three screens - which is the inconsistency the glossary exists to record, and picking one by frequency would settle it wrongly as often as rightly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tures test: add import mapping fixtures
Signed-off-by: Sridhar Bala <sridharkalaibala@gmail.com>
The sixty-one action tooltips are translated in all seventeen languages, and with the last report titles filled every pack answers every key the interface asks for. Two thousand nine hundred and twenty-five of them. That leaves one source of English a shopkeeper still reads every day: the toast. Every save, delete and refusal comes back from the API as a sentence - "Customer added successfully" - and the frontend prints it as it arrives, at five hundred and seventy-six call sites. No tag and no key can reach it. The API cannot carry the key itself. It ships separately, so a till on a new build talking to a server one release behind would be handed a key it has never seen and would print it raw. The English is the identity instead, which is what gettext has done for thirty years: PosnicPro.i18n.say() looks the sentence up by its own words, ignoring case and runs of space, and hands back exactly what it was given when it does not know it. A missing file, a missing entry and an unrecognised sentence all read as they do today. The words live in languages/server/<code>.json and the build ships them as msg-<code>.json beside the pack. Seven hundred and forty-five sentences are listed and none is translated yet, so nothing changes on screen until they are - which is the point: this can land now and improve one language at a time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…oasts The last eleven keys are filled - ten report titles and the alignment button that six packs had left behind - so all seventeen languages now answer every one of the 2,925 keys the interface asks for. The first half of what the server says is translated into French: 373 of 745 sentences, shipped as msg-fr.json beside the pack. A till set to French now reads "Client ajouté" where it read "Customer added successfully". The other half, and the other sixteen languages, fall back to the English exactly as before, which is what makes this safe to land in pieces. docs/TRANSLATING.md explains why the English is the key and warns about the one way to get it wrong: retyping the sentence instead of copying it, which fails silently because the fallback is that same English.
The service worker caches the shop's language pack on first use, so a Tamil till that goes offline keeps its screens in Tamil. The sentences the server sends now live in a second file beside it, and the pattern did not match it - so that same till would have kept every screen in Tamil and answered every save in English. msg-<code>.json rides along, on the same stale-while-revalidate terms and swept by the same versioned cache name on release.
A contributor's pull request is checked by a workflow that asks three questions a maintainer would: is it still valid JSON, did the encoding survive, did anything outside the language files change. It did not look inside languages/server/, where the sentences the API sends are answered. It does now, and it asks a fourth question that only matters there: is this English still a sentence the server sends. The key IS the English, so a retyped one fails silently - the fallback is that same English, and the translation is simply never found.
All 745 sentences the API sends are translated into French, so a French till now reads its own language on every save, refusal and import, not only on the screens. Arabic, German, Hindi and Spanish answer the first 373. The rest still fall back to the English the server sent, which is what it does today, so each language improves on its own without waiting for the others.
A language is now two things: the screens, which every pack answers in full, and the sentences the server sends, which only French answers in full. The table said "all" and meant the first, which reads as finished when a third of what a cashier sees in a day is still English. Also corrects the seeding note, which still quoted 662 keys. It is 2,925.
A 401 sends the cashier back to the sign-in page carrying the reason, which is shown as a toast - so it now goes through the same lookup as every other sentence the server sends. The fallback used when the server gave no reason was a bare English literal with no key; it has one.
Three languages now read their own words on every sentence the server sends, and three more answer the first half. The rest still fall back to the English the server sent.
Five languages now read their own words on every sentence the server sends.
Four tools spawned i18n-coverage.js --json and parsed six hundred kilobytes of JSON out of a pipe. It truncates. Every local run was fine and CI died mid-string at position 182714, which is exactly the shape of a bug that only appears where it costs the most. The module already returns that data, so they call it.
sridharkalaibala
force-pushed
the
develop
branch
from
September 4, 2026 08:17
e377bbf to
8c3bd79
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation
ode --test tests/i18n.test.js (45 tests)
The full local desktop suite starts successfully but this isolated worktree lacks generated frontend assets and optional jsdom/mongodb dependencies; GitHub CI provides that fully provisioned validation.