Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Latin To Cyrillic Laravel Package

shoyim/latin-to-cyrillic

A Laravel/PHP package for converting Latin text to Cyrillic. It offers simple transliteration helpers for turning Uzbek Latin script into Cyrillic, making it easy to localize names, content, and user input in Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This release targets the 21 October 2026 alphabet reform, so treat it as a content-migration release first and a feature release second. Start by reading CHANGELOG.md (the notes are a summary) and diffing the mapping tables against the version you have pinned.

  1. Check your PHP floor before upgrading. The package now declares php >=7.0. That is a minimum, not a target — if you are on Laravel 10/11 you are already on PHP 8.1+, so the constraint is harmless, but it does mean the package cannot use typed properties, enums, or match, and you should not expect PHP 8-only fixes. Confirm your own composer.json platform config does not resolve to an ancient lock.
  2. Pin the previous minor version ("require": {"vendor/package": "1.2.*"}) in a branch and generate a golden-file fixture set before upgrading. You need a "before" snapshot to diff against, because the alphabet change alters output for inputs that previously worked fine.
  3. Normalise a sample of your real data — pull 200–500 representative strings from production (names, addresses, product titles) into a fixture file. Run them through both versions and diff. The two changes that will show up are the new letters (Oʻ→Ö, Gʻ→Ğ, Sh→Ş, Ch→Ç) and the apostrophe/e fixes.
  4. First real use case: a one-off backfill. If you store Cyrillic and render Latin on the fly, this is a pure read-path upgrade — deploy and verify. If you persist Latin (search indexes, slugs, exports, PDFs, third-party syncs), you have stored data in the pre-2026 orthography and need a migration pass using the new converter plus fromNewLatin() to validate round-trips.

Do not skip step 3. The bug fixes for apostrophes and e mean some conversions that "looked wrong" now look different, and some that looked right may also change.

Implementation Patterns

Single conversion boundary. Pick one canonical storage form — I recommend Cyrillic or the new Latin — and convert only at the edges (presentation, export, search indexing). Do not mix old-Latin and new-Latin in the same column; you will never be able to tell them apart afterwards.

Accessor / cast for display:

protected function displayName(): Attribute
{
    return Attribute::get(fn () => $this->latin_name
        ?? app(Converter::class)->toLatin($this->name));
}

Keep the converter resolved from the container rather than new-ing it, so config (and any future mapping overrides) applies consistently.

Backfill as a chunked, idempotent command. Conversion is pure, so it is safe to re-run, but do it in chunks and write a converted_at / orthography_version column so you can resume and audit:

Model::whereNull('latin_v2')->chunkById(500, function ($rows) use ($converter) {
    foreach ($rows as $row) {
        $row->forceFill([
            'latin_v2' => $converter->toLatin($row->name_cyr),
            'orthography_version' => 2,
        ])->saveQuietly();
    }
});

saveQuietly() avoids firing observers that might re-trigger conversion.

Round-trip validation. Use fromNewLatin() as a checker, not just a parser. For each backfilled row, convert forward, then back, and flag rows where the round-trip does not return the source. Those are exactly the strings where the apostrophe or e fixes changed behaviour, and they are where your manual review should go.

Dual-read during rollout. Add a latin_v2 column rather than overwriting. Render from latin_v2 when present, fall back to live conversion. This lets you roll back the release without a data restore.

Search and slugs need their own mapping. Str::slug() will ASCII-fold Ö Ğ Ş Ç to O G S C, which is usually acceptable but will silently collapse distinct names. If you need stable slugs, generate them from the pre-conversion source and store them; do not regenerate after the orthography change or every URL shifts.

Queue the bulk work, and make the job accept an explicit converter version so retries after a deploy do not mix mapping tables.

Gotchas and Tips

  • This is a silent output change. No exception is thrown, no deprecation is emitted — the same input simply produces Ş where it used to produce Sh. Any test asserting exact transliterated strings will fail, and any snapshot/export diff will be noisy. Budget for it.
  • Apostrophe variants are the number-one source of bugs. Oʻ may arrive as U+02BB (modifier letter turned comma), U+2018, U+2019, a plain ASCII ', or a backtick, depending on the input source (Word, Excel, copy-paste, an old CMS). Normalise to a single codepoint before calling the converter, and normalise to NFC. The release fixed apostrophe handling, but it cannot fix your upstream data.
  • fromNewLatin() is not a guaranteed inverse. Diacritics remove information: the new alphabet encodes digraphs as single letters, and the reverse mapping has to guess case and word boundaries. Round-trip Ş/Ç across morpheme seams (e.g. ch spanning a prefix and root) and verify. Treat round-trip failures as expected for a small tail, not as a bug in your code.
  • Case handling is not automatic. Sh, SH, sh, and sH are different inputs. Check whether the converter case-folds internally; if not, use mb_strtolower/mb_convert_case(..., MB_CASE_TITLE) with the correct locale, and be aware MB_CASE_TITLE behaves oddly on digraphs.
  • Requires mbstring. Multibyte functions are mandatory; add ext-mbstring to your composer.json explicitly rather than relying on it being present in the base image.
  • Database columns and collation. Ö Ğ Ş Ç are fine in utf8mb4, but a utf8mb3/latin1 column or a ..._general_ci collation will mangle or over-match them. Verify the columns you backfill are utf8mb4_unicode_ci (or unicode_520_ci).
  • Configuration is likely publishable. Run php artisan vendor:publish and check whether mapping tables or a "legacy mode" toggle are exposed. If a legacy/pre-2026 mapping exists, keep it config-driven rather than forking the package.
  • PHP 7.0 floor means no modern syntax inside the package. If you are debugging, don't expect readonly, enums, or named arguments in vendor code — and if you extend its classes, keep your overrides signature-compatible.
  • New PHPUnit tests and CI are a good sign but check the matrix. A >=7.0 floor usually means the CI matrix starts at 7.0 and may not exercise PHP 8.3/8.4. Run the suite locally on your actual PHP version before trusting green.
  • Debugging tip: convert a single string with bin2hex() on the input first. Nine out of ten "the converter is wrong" reports are a non-breaking space, a curly apostrophe, or an NFD-decomposed Ö (O + U+0308).
  • Rollback plan: because the change is data-level, rolling back the package version is not enough — you also need the latin_v2 column and the old code path intact. Ship the column first, backfill second, flip reads third.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource