Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Translation] Make CrowdinProvider::write() add the locales missing from the project - #66028

Merged
nicolas-grekas merged 1 commit into
symfony:8.2from
Amoifr:feat-64155-crowdin-write-missing-locales
Sep 13, 2026
Merged

[Translation] Make CrowdinProvider::write() add the locales missing from the project#66028
nicolas-grekas merged 1 commit into
symfony:8.2from
Amoifr:feat-64155-crowdin-write-missing-locales

Conversation

@Amoifr

@Amoifr Amoifr commented Sep 12, 2026

Copy link
Copy Markdown
Contributor
Q A
Branch? 8.2
Bug fix? no
New feature? yes
Deprecations? no
Issues Part of #64155
License MIT

This covers the Crowdin write() item of the checklist in #64155. Loco, Lokalise and Phrase create the locales a project is missing, Crowdin logged Ignored "%s" locale because it is not configured or mapped in the project. and skipped them.

write() now adds the target languages the project does not have yet, in one PATCH on the project before the upload, keeping the languages already configured:

[{"op": "replace", "path": "/targetLanguageIds", "value": ["fr", "de"]}]

The locale is spelled as a Crowdin language ID, with a dash where Symfony uses an underscore, which is the reverse of what getLanguageIds() already does when reading them. A locale that the project maps under another name is left alone, since it is already known.

Editing a project requires a token with a read and write project.settings scope, which is the concern raised in the issue. A narrower token cannot add anything, so the failure is logged and write() then skips those locales exactly as it does today: no exception, and the locales that do exist are still pushed.

Tests cover both paths, the project being patched with the union of its languages, and the 403 case where the locales are skipped and the source file is still written. Checked with mocked responses only, not against a live Crowdin project.

@nicolas-grekas

Copy link
Copy Markdown
Member

Rebased on 8.2 and amended before merge.

The locale to language id conversion was str_replace("_", "-", $locale). Of the 314 languages Crowdin lists, only es-ES, la-LA and pt-PT use that shape, so fr_FR, de_DE, it_IT, ja_JP and most European locales produced an id that does not exist and the patch came back with Language fr-FR not found. write() now reads GET /languages once, only when a locale is missing, and maps a locale to an id through the locale field each language carries, preferring an exact id match for the three ambiguous cases.

Unsupported locales are now left out of the patch instead of travelling with the others, so a single unknown locale no longer stops every other one from being added; they keep falling back to the Ignored "%s" locale ... warning. A locale whose language the project already has, such as fr_FR against fr, no longer triggers a patch at all.

A 5xx on either request now throws like the rest of the bridge, while a 403 still only logs and skips. Added an UPGRADE-8.2.md note about the project.settings write scope, plus four tests.

I left out an RFC 6902 test op guarding the read-modify-write window: test on an array compares member by member including order, nothing promises Crowdin returns targetLanguageIds in a stable order, and a Crowdin that does not honour it would turn every push into a 400. The window is one round trip.

@Amoifr

Amoifr commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the rebase and the amend, and above all for catching the language id mapping. I had assumed str_replace('_', '-', $locale) was enough, and reading GET /languages once is clearly the right call when only 3 of the 314 languages use that shape. Leaving unsupported locales out of the patch instead of letting one of them sink the others is a real improvement too.

On the current red, Psalm fails on the new getSupportedLanguageIds():

CrowdinProvider.php:563 InvalidReturnType: declared 'array<string, string>', got 'array<array<array-key, string>|string, mixed>'

$language['data']['locale'] comes out of toArray() as mixed, so Psalm allows str_replace() to return an array and never narrows the key to string. Casting before use should settle it:

foreach ($response->toArray()['data'] as $language) {
    $id = (string) $language['data']['id'];
    $languageIds[str_replace('-', '_', (string) $language['data']['locale'])] ??= $id;
    $languageIds[str_replace('-', '_', $id)] = $id;
}

Want me to push that, or would you rather fold it into your amend?

The two unit test reds look unrelated: the same testSchemeRedirect and DoctrineOutboxIntegrationTest failures show on #65989, and both were fixed on 8.2 right after this run (6cc2cdda and b96ffaca). A rebase should clear them.

…rom the project

Loco, Lokalise and Phrase create the locales a project does not have yet, while
Crowdin logged them as ignored and skipped them. The project is now patched with
the languages it is missing before the upload.

Crowdin spells its language IDs its own way, so the list of the languages it
supports is fetched to turn a locale into an ID: "fr_FR" is the "fr" language and
"es_ES" is the "es-ES" one. A locale Crowdin does not know is left out of the
patch, so the other ones are still added, and it keeps falling back to the
warning write() already logged.

Editing a project needs a token with a read and write "project.settings" scope,
so a failure is logged and those locales are skipped as they were before, rather
than throwing. A server error still throws, like the rest of the bridge.
@nicolas-grekas
nicolas-grekas force-pushed the feat-64155-crowdin-write-missing-locales branch from 1caeba4 to 3db1be0 Compare September 13, 2026 19:56
@nicolas-grekas

Copy link
Copy Markdown
Member

Thanks, and you are right on both counts. The Psalm error is mine, so I folded the casts into the amend rather than have you push a fix for it: toArray() hands back mixed, so str_replace() could return an array as far as Psalm knows, and the key never narrows to string. Taking $id once as a (string) also reads better than casting it twice.

Rebased on 8.2 as well, so the testSchemeRedirect and DoctrineOutboxIntegrationTest reds should go with it. Crowdin bridge green, 42 tests.

@nicolas-grekas

Copy link
Copy Markdown
Member

Thank you @Amoifr.

@nicolas-grekas
nicolas-grekas merged commit 9420168 into symfony:8.2 Sep 13, 2026
11 of 13 checks passed
@Amoifr

Amoifr commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

The rebase cleared both unit test reds as expected. Two things left here.

Psalm still reports the same two errors on getSupportedLanguageIds(), so the question stands: want me to push the cast, or would you rather fold it into your amend?

x86 / minimal-exts / lowest-php went red after the rebase, but it does not look related to this PR: #66040 and #66033 are red on that same job right now, each on a different AmpHttpClientTest timeout test (testTimeoutWithActiveConcurrentStream and testTimeoutOnInitialize, against testNotATimeout and testTimeoutIsNotAFatalError here). A different test each run on the same job, and the CI script already notes that HttpClient tests time out unless run separately.

@nicolas-grekas

Copy link
Copy Markdown
Member

I don't care about psalm if it doesn't spot bugs - so unless those reports are about bugs, it's fine not pursuing the goal of making SA tools happy for the sake of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants