Skip to content

feat: cache the parsed translation files, keyed by their content - #14

Merged
JDCyllene merged 5 commits into
CylleneDigital:mainfrom
JDCyllene:feat/translation-files-cache
Oct 15, 2026
Merged

JDCyllene merged 5 commits into
CylleneDigital:mainfrom
JDCyllene:feat/translation-files-cache

Conversation

@JDCyllene

@JDCyllene JDCyllene commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

What this pull request does

Lot C of the changes the Sylius integration asked for: the bundle no longer parses every translation file again in each new process.

  1. Parsed files are cached in the pool cyllene_digital_ai_translation.translation_files_cache, under a key made of the file's path, a hash of its content and a format version (TranslationFileScanner::CACHE_FORMAT). Nothing invalidates an entry: a file whose content changes gets a new key, and the pool's 7-day lifetime removes what is left behind. The walk of the directories is not cached, so a file added or removed is seen at once. A file that cannot be parsed is cached as such, and still reported in the ignored files when served from the cache. A file that cannot be read is reported by the scanner itself and never cached.
  2. The pool is declared by the bundle with adapter: cache.app and a 7-day lifetime (prepended into framework.cache.pools); a host entry with the same name changes either. Its name joins the public contract.
  3. translation_files_cache (boolean, default true): false parses the files in every new process, as before.
  4. TranslationFileScanner implements ResetInterface: its in-memory memo is emptied between two Messenger messages, so a worker sees an edited file without a restart.

Measured on the 58 XLIFF files of symfony/demo (3,191 entries): about 230 ms to scan without the cache, about 5 ms with it (content hashed).

ADR 0027; docs/operations/cache.md, docs/configuration-reference.md, docs/operations/multi-server.md, docs/public-contract.md and CLAUDE.md are updated.

Points to watch

  • Tests pass (vendor/bin/phpunit): SQLite, MariaDB 11.8, PostgreSQL 17, without intl and with the lowest dependencies
  • PHPStan and PHP-CS-Fixer are green (vendor/bin/phpstan analyse, vendor/bin/php-cs-fixer check)
  • UPGRADE.md is up to date if the public contract changes: an addition only, nothing to upgrade
  • A decision taken or reversed has its record in docs/adr/: 0027
  • No real API key appears in the code, the tests or this description

No change to the entity mapping. The configuration tree gains one optional key with a default: the Flex recipe stays as it is.

For review:

  • After review, the key is the content's hash rather than the date of modification and the size: a file rewritten within the same second at the same size, or a release deployed under the same path with the dates a build froze on a shared pool, is no longer served stale. ADR 0027 lists the date and size among the alternatives rejected.
  • The loaders' version is not part of the key: a parsing change in symfony/translation is seen once the entry expires, or with bin/console cache:pool:clear cyllene_digital_ai_translation.translation_files_cache.
  • On Symfony 7.4+, APP_SHARE_DIR (set by the skeleton to var/share) holds cache.app, which then survives cache:clear: docs/operations/cache.md says to empty the pool with the command above.
  • Tested in a copy of the sandbox (prod): an edited XLIFF file is seen at once, cache:pool:clear empties the pool, and a host entry under framework.cache.pools overrides the adapter while the lifetime is kept.

…, date and size

A view over every catalogue parsed every translation file again in each new process.
TranslationFileScanner now keeps each parsed file in the pool
cyllene_digital_ai_translation.translation_files_cache (adapter cache.app, 7 days, both
overridable under framework.cache.pools), keyed by its path, date of modification, size and
a format version. A file that changes gets a new key; the walk of the directories is never
cached. An unparseable file stays reported when served from the cache.

On by default, translation_files_cache: false turns it off. The scanner implements
ResetInterface, so a Messenger worker sees an edited file between two messages.

ADR 0027.
@JDCyllene
JDCyllene requested a review from a team as a code owner October 15, 2026 10:53
…che an unreadable one

The date of modification and the size missed a file rewritten within the same second at the
same size, and a release deployed under the same path, with the dates a build froze, on a pool
shared between releases. Reading and hashing a file costs a fraction of reading its cache
entry. A file that cannot be read is now reported by the scanner itself (the JSON loader read
it as empty) and never cached: a chmod changes nothing a key could see.
@JDCyllene JDCyllene changed the title feat: cache the parsed translation files, keyed by path, date and size feat: cache the parsed translation files, keyed by their content Oct 15, 2026
@JDCyllene
JDCyllene merged commit 935255e into CylleneDigital:main Oct 15, 2026
12 checks passed
@JDCyllene
JDCyllene deleted the feat/translation-files-cache branch October 15, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant