Skip to content
Β 
Β 

Repository files navigation

Jigsaw Localization

Tests PHPStan Packagist

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.

Installation

composer require libresign/jigsaw-localization

Register the localization loader in bootstrap.php:

use LibreSign\JigsawLocalization\LoadLocalization;

$events->beforeBuild([LoadLocalization::class]);

By default, translation catalogs are loaded from /lang.

Translation catalogs

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.

Custom translation directory

use LibreSign\JigsawLocalization\LoadLocalization;

$loader = new LoadLocalization('/path/to/translations');
$translations = $loader->load();

Custom localization loader

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);

Default locale

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.

Translating strings

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.

Locale-aware paths and URLs

current_path_locale()

Returns the locale resolved for the current page:

$currentLocale = current_path_locale($page);

translate_path()

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

translate_url()

Equivalent to translate_path(), but returns a URL using Jigsaw's configured base URL:

translate_url($page, 'fr');

locale_path()

Builds a path for the current locale:

locale_path($page, '/contact');

locale_url()

Equivalent to locale_path(), but returns a URL:

locale_url($page, '/contact');

Source-string collection

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.

Source extraction

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.

Catalog operations

Reading and writing JSON

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.

Synchronizing catalogs

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,
);

Validating placeholders

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.

Safe catalog paths

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.

About

πŸ“¦πŸŒ A localization library for the static site generator 'tightenco/jigsaw' using JSON

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages