fix(i18n): use singular customer key in transaction modal - #482
fix(i18n): use singular customer key in transaction modal#482sridharkalaibala wants to merge 788 commits into
Conversation
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.
Owner: "demo data and demo users required in that. give option to easy reset or seeders in that. i want full fledged dev server." seed.js installs through the REAL /api/install/add - the same path a genuine tenant takes - rather than writing documents into the database. A fixture that bypasses production code tests the fixture. Four accounts, because a contributor changing anything near permissions needs to see a screen as somebody who is NOT an administrator, and the only way to make that easy is to hand them the login. Their access maps are built by REMOVING modules from the administrator's own, so they stay valid as screens are added; a hand-written permission object goes stale the first time somebody adds one. Two bugs found by running it rather than reading it: the first version set first_name/last_name/role, and all four accounts logged in as "Aarti" with usertype super_admin - four ways to be the administrator, which is the opposite of the point. The fields the app reads are firstname/lastname/usertype and `access`. api/scripts/local-dev.js sends demo_data: 'yes' and the install service reads only register_demo, so the local development shop has been coming up EMPTY for as long as that script has existed. Fixed here too. The installer's own demo activity is wrapped in a try/catch that deliberately swallows failures, and on this box produced no sales without saying why. For a dev server that is not good enough - reports, returns and the dashboard all read as empty, which is exactly the set of screens most worth testing - so the history is built from the same demo-seed module the demo shops use. The nightly reset reseeds instead of emptying. A blank sandbox every morning is the problem the seeder exists to solve. The logins are shown on the login page. A wiki or a Notion doc is one more place to look, one more thing to keep in step, and the first thing to go stale; these are safe to publish because the box is public, holds demo data and is wiped nightly. Now: 30 items, 14 sales, 5 receivings, 4 users, one branch.
Owner: "i see demo logins repeated many times. also not properly aligned." Two separate faults, and the second was hiding behind the first. sub_filter_once was off, and it applies PER STRING rather than per block - so the panel was injected before EVERY <form> on the login page, four times. It is on now. Underneath that, Cloudflare's Email Address Obfuscation had rewritten all four addresses into [email protected] links that only resolve with JavaScript - on the one page where the address IS the content. The panel is now wrapped in <!--email_off-->, which is Cloudflare's own documented opt-out. Worth knowing for any other page that has to show an address. The layout was also asking the reader to assemble an address in their head while trying to sign in: "manager@ . cashier@ . staff@ - same domain". Each one is written out now, in a left-aligned table of role, address and what that person can reach. Verified on the served page rather than in the config: one panel, one banner, zero mangled addresses, all four readable.
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.
e377bbf to
8c3bd79
Compare
|
Thank you for this. Before it can be merged we need a one-line signature on our Contributor Licence Agreement. You keep the copyright in your work; the agreement lets Posnic ship your contribution in the paid edition alongside the AGPL release. To sign, reply to this pull request with exactly: I have read the CLA document and I hereby sign the CLA You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. |
|
Reviewed before merging. Recommend closing this rather than merging it, for two reasons, and the second is the one that matters. The change would be a regression in TamilThe heading is
The real complaint is fair: And the diff is nine days of drift948 files, +160,669 / -296,230. Almost all of it is a regenerated If the English map ever does need regenerating, |
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.