mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-11-08 23:43:32 -05:00
- Replace untranslated English strings with empty fallback ('') across all non-English catalogs and sort keys alphabetically (closes #4783, closes #4660)
- Restore missing Sales.no_sales_to_display in English while preserving existing translations (closes #4794, references #4637, #4795)
- Register Georgian (ka) and Swahili (sw-KE, sw-TZ) locales, normalize Croatian to hr per BCP 47, and fix datepicker long month keys (partially addresses #4476, references #3891)
- Hardcode invariant brand constants (OSPOS, website, copyrights) in login/footer views, remove from catalogs, and standardize Latin OSPOS branding
- Fix English catalog typos and maintain consistent British English variants in en-GB
- Add LanguageFilesTest with 4,418 automated tests for syntax, key parity, sorting, and bidirectional locale registrations
- Update AGENTS.md with localization guidelines and development best practices
11 KiB
11 KiB
Agent Instructions
This document provides guidance for AI agents working on the Open Source Point of Sale (OSPOS) codebase.
Code Style
- PSR-12 enforced via PHP-CS-Fixer (config:
.php-cs-fixer.no-header.php) - Follow PHP CodeIgniter 4 coding standards
camelCasefor variables and methods;PascalCasefor classes;UPPER_CASEfor constants- When editing existing code containing non-PSR-compliant local variable names, refactor those variable names to
camelCaseas part of the edit - All newly written code (variables, classes, functions) must use PSR-compliant naming, regardless of surrounding code style
- PHP 8.2+ features acceptable (named arguments, enums, readonly properties)
- Write PHP 8.2+ compatible code with proper type declarations
- Always import classes, functions, and constants with a
usestatement at the top of the file instead of referencing them inline via fully-qualified name (e.g.use Config\Database;thenDatabase::connect(), not\Config\Database::connect()) - No useless comments or docblocks: if the code speaks for itself, explanatory comments are unnecessary. Do not add comments or docblocks that merely restate what the code already makes clear (e.g.
@param array $itemsnext toarray $items, or a comment repeating a method's name) — only comment on non-obvious rationale, constraints, or behavior - Views in
app/Views/errors/html/are excluded from the fixer - Run fixer before committing:
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php - JavaScript: use
constfor variables that are never reassigned,letfor variables that are. Never usevar.
Development
- Create a new git worktree for each issue, based on the latest state of
origin/master - Commit fixes to the worktree and push to the remote
Testing
- Run PHPUnit tests:
composer test - Tests must pass before submitting changes
- One test file per class under test. A controller, model, library, or helper gets exactly one test file covering all of its behavior —
Item_kits.php→tests/Controllers/Item_kitsTest.php(orItem_kitsControllerTest.php, matching this codebase's existing*ControllerTest.phpsuffix for controllers),Sale_lib.php→tests/Libraries/Sale_libTest.php, etc. Do not create feature- or endpoint-scoped test files alongside a class's main test file (e.g. noItem_kitsBarcodeTest.phpnext toItem_kitsControllerTest.php) — add the new test methods to the existing file for that class instead. If no test file exists yet for the class, create the one canonical file rather than a narrowly-scoped one. - Name test methods as
test{MethodUnderTest}_{Behavior}.PascalCasethe method under test, then an underscore, then a short behavior phrase —testPostSave_RejectsNegativeTaxPercent,testSaveLocale_AcceptsValidReferenceCodeMinMax. Start the behavior with a verb:Accepts/Requiresfor valid or boundary cases,Rejectsfor invalid ones. The name should read as a self-documenting sentence and needs no docblock. - Resetting the test database (full migration reset, not just
$refresh). When a test class needs theospos_testsschema fully dropped and re-migrated from scratch (e.g. soospos_permissions/app_configget freshly seeded),use Tests\Support\DatabaseBootstrapTrait;and call$this->resetDatabaseOnce()as the first line ofsetUp(), beforeparent::setUp():use Tests\Support\DatabaseBootstrapTrait; class FooTest extends CIUnitTestCase { use DatabaseTestTrait; use DatabaseBootstrapTrait; protected $migrate = true; protected $migrateOnce = true; protected $seedOnce = true; protected $refresh = false; protected $namespace = null; protected function setUp(): void { $this->resetDatabaseOnce(); parent::setUp(); // ... } }DatabaseBootstrapTrait(tests/Support/DatabaseBootstrapTrait.php) wrapsTestDatabaseBootstrapSeeder::reset()with a per-class static guard so it only runs once per test class. Do not reinvent this with a localprivate static bool $doneBootstrapflag,setUpBeforeClass(), orConfig::seeder($this->DBGroup)->call('App\Database\Seeds\TestDatabaseBootstrapSeeder')followed by a manualConfig::connect($this->DBGroup)->close()/resetDataCache()in the test file — those are the old patterns the trait replaces, and the manual version reintroduces the exact stale-connection bugTestDatabaseBootstrapSeeder::reset()already fixes internally. Only add this trait to test classes that actually touch the real test database (DatabaseTestTraitusers) — pure unit tests that mock everything don't need it. If a test class readsconfig(OSPOS::class)->settingsfor real (not mocking it out), also callconfig(OSPOS::class)->update_settings();right afterparent::setUp();to force a fresh settings read post-reset. Seetests/Controllers/ReportsControllerTest.phpfor that shape. - Mocking OSPOS config settings. When a test needs
config(OSPOS::class)->settingsto return fixed values instead of reading from the database,use Tests\Support\OsposConfigFixtureTrait;and call$this->injectOsposSettings([...])instead of manually constructingnew OSPOS()+Factories::injectMock('config', OSPOS::class, $ospos):use Tests\Support\OsposConfigFixtureTrait; class FooTest extends CIUnitTestCase { use OsposConfigFixtureTrait; protected function setUp(): void { parent::setUp(); $this->injectOsposSettings([ 'number_locale' => 'en_US', 'currency_decimals' => 2, ]); } }OSPOS::getDefaultSettings()isprivateand only covers a handful of keys, so it is not a usable base to merge onto — each test must supply the exact settings it depends on. Use this trait regardless of whether the test also usesDatabaseBootstrapTrait.
Build
- Install dependencies:
composer install && npm install - Build assets:
npm run buildorgulp
Conventions
- Controllers go in
app/Controllers/ - Models go in
app/Models/ - Views go in
app/Views/ - Database migrations in
app/Database/Migrations/ - Use CodeIgniter 4 framework patterns and helpers
- Sanitize user input; escape output using
esc()helper
Localization
- Key Parity: All catalogs must maintain the exact same keys as
app/Language/en/(no extra or missing keys). Adding a key requires adding it toen/with the English source text, and to all otherapp/Language/*/files (using''if untranslated, includingen-GB). - Alphabetical Ordering: All catalogs must be sorted alphabetically by key, except
Calendar.php(ordered chronologically by day and month sequences). - Direct Fallback to
en: Non-English files use''for untranslated strings to trigger dynamic fallback toen(nl-NL > en,nl-BE > en,hr > en,en-GB > en). - British English (
en-GB): Only include strings differing in UK spelling/vocabulary (Cheque, Centre, Post Code). Keep identical strings as''to inherit updates fromen. - Locale Naming (BCP 47):
- General languages: Use bare ISO language codes (
hrnothr-HR,fr,it,da,ckb). Do not add redundant country tags when a language has no regional distinction. - Regional / dialect variants: Use
{lang}-{REGION}when targeting a specific regional dialect (nl-NL&nl-BE,de-DE&de-CH,es-ES&es-MX,en-GB,pt-BR). Chinese uses script subtags (zh-Hans,zh-Hant).
- General languages: Use bare ISO language codes (
- Locale Registration: Register new locales in both
app/Config/App.php($supportedLocales) andapp/Helpers/locale_helper.php(get_languages(), format:'code:name' => 'English Name'). - Calendar Convention (
mayvsmayl):mayis the 3-letter abbreviation;maylis the full month name (May long). Datepickers and full-month arrays must referencelang('Calendar.mayl'), while short-month arrays referencelang('Calendar.may'). - Translation Integrity:
- When deprecating or renaming keys, always migrate existing non-English human translations to the new key/module. If a legacy translation references the wrong entity (e.g. 'item' in Attributes), reset it to
''. - When an English string in
app/Language/en/is changed or rewritten, reset the corresponding non-English strings inapp/Language/*/to''so they dynamically fall back to the updated English text and signal to translators that a new translation is required.
- When deprecating or renaming keys, always migrate existing non-English human translations to the new key/module. If a legacy translation references the wrong entity (e.g. 'item' in Attributes), reset it to
- Placeholders & ICU Formatting:
- Interpolate user-facing strings with
lang()and named placeholders (e.g.{company}) rather than string concatenation or positional placeholders ({0}). - Translations must preserve all placeholders (
{0},{name}) and literal keys/filenames untranslated (reordering within the sentence to fit natural grammar is encouraged); never omit them. - Placeholder-bearing translations use ICU
MessageFormatter: double literal braces ({{/}}) and apostrophes ('') to avoid syntax warnings.
- Interpolate user-facing strings with
- Punctuation & Wrappers:
- No leading or trailing whitespace in strings; never rely on trailing spaces for concatenation.
- If an English string is enclosed in brackets
[...]or parentheses(...), translations must preserve that enclosure (or localized fullwidth equivalents like【...】/(...)). - Watch for duplicate punctuation typos (e.g.
::or..instead of ellipsis...). - Indicator flags (
taxed_ind,nontaxed_ind) and report badges (Reports.code_*) must be kept compact (single characters or short abbreviations, never full sentences or punctuation placeholders like'-','.'). Untranslated indicators must remain''to inherit fromen.
- Language File Syntax & Formatting:
- Only
app/Language/en/andapp/Language/en-GB/may contain English prose. Universal brand names and acronyms (ID,SMS,POS,Mailchimp,CSV) are acceptable in non-English files, but never copy English sentences or phrases; leave as''to inherit fromen. Datepicker.weekstartis a numeric calendar setting (0= Sunday,1= Monday,6= Saturday), not English text — never wipe it to''.- Enclose keys and strings in
'(escape internal quotes as\'). Array keys must be unique. - Align
=>to match the file's standard column padding (e.g. column 40). Avoid unnecessarily re-padding entire catalogs for a single long key. - Plugin language files (
app/Plugins/*/Language/) follow the same rules.
- Only
- Validation: Run
vendor/bin/phpunit tests/LanguageFilesTest.phpto verify syntax, key parity, sorting, duplicate rejection, and locale registration.
Security
- Never commit secrets, credentials, or
.envfiles - Use parameterized queries to prevent SQL injection
- Validate and sanitize all user input
- Never reference security advisory IDs (CVE, GHSA, etc.) in code, comments, commit messages, docblocks, documentation, or URLs — treat them the same as secrets. They act as a roadmap for attackers researching the exact exploit a fix addresses.