Localization support for Jigsaw using JSON translation catalogs.
This project is a maintained fork of elaborate-code/jigsaw-localization, originally created by Elaborate Code.
The package provides:
- JSON translation loading for Jigsaw;
- locale-aware path and URL helpers;
- source-string collection and extraction;
- deterministic catalog read/write operations;
- translation catalog synchronization and validation.
composer require libresign/jigsaw-localizationRegister the localization loader in bootstrap.php:
use LibreSign\JigsawLocalization\LoadLocalization;
$events->beforeBuild([LoadLocalization::class]);By default, translation catalogs are loaded from /lang.
Create one directory per locale:
lang/
βββ en/
β βββ main.json
βββ es/
β βββ main.json
βββ pt-BR/
βββ main.json
Each catalog is a flat JSON object whose keys are source strings:
{
"Good morning": "Bom dia",
"Sign document": "Assinar documento"
}The source locale can use the source text as both key and value:
{
"Good morning": "Good morning",
"Sign document": "Sign document"
}Translation files can be maintained manually or generated by any localization workflow.
use LibreSign\JigsawLocalization\LoadLocalization;
$loader = new LoadLocalization('/path/to/translations');
$translations = $loader->load();Projects that store translations somewhere other than JSON files can implement LocalizationLoader and inject it into the Jigsaw listener:
use LibreSign\JigsawLocalization\Contracts\LocalizationLoader;
use LibreSign\JigsawLocalization\LoadLocalization;
$loader = new class implements LocalizationLoader {
public function load(): array
{
return [
'en' => ['Hello' => 'Hello'],
];
}
};
$localization = new LoadLocalization(loader: $loader);Set defaultLocale in Jigsaw's configuration:
return [
'defaultLocale' => 'en',
];If omitted, the default locale is en.
Locales are resolved from the locales actually loaded into the project. The package does not impose a fixed locale naming convention.
Use the __ helper to retrieve a translation:
echo __($page, 'Good morning');Pass a locale explicitly when needed:
echo __($page, 'Good morning', 'pt-BR');When no locale is provided, the package resolves it from the current page path and falls back to the configured default locale.
Returns the locale resolved for the current page:
$currentLocale = current_path_locale($page);Returns the equivalent page path for another locale:
translate_path($page, 'fr');| Current path | Target locale | Result |
|---|---|---|
/contact |
fr |
/fr/contact |
/fr/contact |
en |
/contact |
/es/contact |
fr-CA |
/fr-CA/contact |
Equivalent to translate_path(), but returns a URL using Jigsaw's configured base URL:
translate_url($page, 'fr');Builds a path for the current locale:
locale_path($page, '/contact');Equivalent to locale_path(), but returns a URL:
locale_url($page, '/contact');For runtime collection, use SourceStringCollector:
use LibreSign\JigsawLocalization\Catalog\SourceStringCollector;
$collector = new SourceStringCollector('en');
$collector->collect('en', 'Hello');
$collector->collect('pt-BR', 'OlΓ‘');
$sourceStrings = $collector->all();
// ['Hello' => 'Hello']Only strings from the configured source locale are collected.
Extraction is based on a generic source/extractor contract.
A TranslationSource identifies the locale, source and contents to inspect. A TranslationStringExtractor implementation decides how strings are found inside that source.
use LibreSign\JigsawLocalization\Extraction\CallbackStringExtractor;
use LibreSign\JigsawLocalization\Extraction\ExtractionPipeline;
use LibreSign\JigsawLocalization\Extraction\TranslationSource;
$extractor = new CallbackStringExtractor(
fn (TranslationSource $source): bool => str_ends_with($source->identifier(), '.md'),
fn (TranslationSource $source): array => [$source->contents()],
);
$pipeline = new ExtractionPipeline('en', [$extractor]);
$catalog = $pipeline->catalog([
new TranslationSource('en', 'posts/hello.md', 'Hello'),
new TranslationSource('pt-BR', 'posts/ola.md', 'OlΓ‘'),
]);The pipeline ignores non-source locales before invoking extractors, preventing translated content from being collected into the source catalog.
For reusable extraction logic, implement TranslationStringExtractor directly.
use LibreSign\JigsawLocalization\Catalog\JsonTranslationCatalog;
$catalog = new JsonTranslationCatalog('lang/en/main.json');
$catalog->write([
'Good morning' => 'Good morning',
]);
$translations = $catalog->read();Catalog output is deterministic and uses JSON objects with UTF-8 content.
use LibreSign\JigsawLocalization\Catalog\TranslationCatalogSynchronizer;
$synchronizer = new TranslationCatalogSynchronizer();
$source = $synchronizer->source([
'Hello',
'Goodbye',
]);
$translation = $synchronizer->translation(
$source,
['Hello' => 'OlΓ‘'],
);Existing translations are preserved. New source keys use the source text as fallback.
Obsolete translated keys are preserved by default. Pruning must be requested explicitly:
$translation = $synchronizer->translation(
$source,
$translation,
pruneObsolete: true,
);use LibreSign\JigsawLocalization\Catalog\TranslationCatalogValidator;
$validator = new TranslationCatalogValidator();
$errors = $validator->validatePlaceholders(
['%s signed %d documents' => '%s signed %d documents'],
['%s signed %d documents' => '%2$d documents signed by %1$s'],
);The validator supports positional printf/vsprintf placeholders so arguments can be reordered safely in translated text.
use LibreSign\JigsawLocalization\Catalog\TranslationCatalogLocator;
$locator = new TranslationCatalogLocator('lang');
$sourcePath = $locator->path('en');
$messagesPath = $locator->path('pt-BR', 'messages');The locator rejects unsafe path segments while leaving locale naming policy to the project.