Repository: Behat/Gherkin (PHP)

Issue:
Changed behavior in v4.15 when parsing test files with different languages
I noticed a failing CI job in https://github.com/Codeception/Codeception/actions/runs/19833673725/job/56825691142 when `behat/gherkin:4.15` is used.

The same tests are green when using `behat/gherkin:4.14`.

Codeception uses [`Behat\Gherkin\Keywords\CachedArrayKeywords::withDefaultKeywords()`](https://github.com/Codeception/Codeception/blob/71087f3e643119fae473644ffd5f73ece113b80d/src/Codeception/Test/Loader/Gherkin.php#L73) when initializing the lexer.

The test tries to load all gherkin files.

File 1 and 3 are using the default language "en" whereas the language "ru" is defined for file 2.

File 1

```gherkin
Feature:
  In order to know that the right step will be executed
  As a user
  I should be notified about steps that are ambiguous

  Scenario: We have multiple step definitions that match a given step
    Given a step which matches multiple step definitions
```

File 2

```gherkin
#language: ru

Функционал: Генерация сниппетов из сценариев на других языках
  Как пользователь
  Чтобы выполнить проверку сценариев на родном языке заказчика
  Я хочу иметь возможность генерировать сниппеты из них 

  Сценарий: Генерация сниппета из сценария на русском языке
    Допустим я написал сценарий на языке "RU_ru"
```

File 3

```gherkin
Feature: Run gherkin
  In order to test a feature
  As a user
  I need to be able to have subfolders

  Scenario: Describe a feature in subfolder
    Given I have a feature in a subfolder
```

In `behat/gherkin:4.14` this works without problems.
However, in `behat/gherkin:4.15` the lexer seems to use the language "ru" for file 3 and thus parsing the file fails.

The test becomes green when adding `#language: en` to file 3.

I'm pretty sure this is related to https://github.com/Behat/Gherkin/pull/350 because before this PR, "en" was used as second parameter when calling `Lexer::analyse()` inside `Parser::parse()`.

Is the behavior change intended or does codeception just use `behat/gherkin` the wrong way?


Discussion:
This behavior change is not intended. We might have missed the case of parsing multiple files using different languages with the same parser instance. As our testsuite isolates each tests, I don't think we have cases reusing the parser right now.
However, `analyze` should be initializing the current dialect with the default dialect each time so this should be working AFAICT.

Can you try initializing the lexer with a `CucumberDialectProvider` instead, which is the equivalent of `CachedArrayKeywords::withDefaultKeywords()` but using the modern API ? Maybe the bug happens only in the BC layer adapting old keywords.
> Can you try initializing the lexer with a CucumberDialectProvider instead, which is the equivalent of CachedArrayKeywords::withDefaultKeywords() but using the modern API ?

Works like a charm - thank you for the quick help!
Then this confirms that the bug is in the BC layer. I will work on a fix when I can. Anyway, I suggest that Codeception migrates to the modern API.
I identified the root cause of the issue. I will work on a fix.
@stof has fixed this - the fix will be in 4.16.0 which we'll release in the next couple of days. Apologies for the inconvenience and thanks for reporting it.



Patch:
diff --git a/src/Dialect/KeywordsDialectProvider.php b/src/Dialect/KeywordsDialectProvider.php
index bca44e1a..c5390601 100644
--- a/src/Dialect/KeywordsDialectProvider.php
+++ b/src/Dialect/KeywordsDialectProvider.php
@@ -21,9 +21,13 @@
  */
 final class KeywordsDialectProvider implements DialectProviderInterface
 {
+    private readonly string $defaultLanguage;
+
     public function __construct(
         private readonly KeywordsInterface $keywords,
     ) {
+        // Assume a default dialect of `en` as the KeywordsInterface does not allow reading its language but returns the current data
+        $this->defaultLanguage = $this->keywords instanceof ArrayKeywords ? $this->keywords->getLanguage() : 'en';
     }
 
     public function getDialect(string $language): GherkinDialect
@@ -40,10 +44,9 @@ public function getDialect(string $language): GherkinDialect
 
     public function getDefaultDialect(): GherkinDialect
     {
-        // Assume a default dialect of `en` as the KeywordsInterface does not allow reading its language but returns the current data
-        $language = $this->keywords instanceof ArrayKeywords ? $this->keywords->getLanguage() : 'en';
+        $this->keywords->setLanguage($this->defaultLanguage);
 
-        return $this->buildDialect($language);
+        return $this->buildDialect($this->defaultLanguage);
     }
 
     private function buildDialect(string $language): GherkinDialect


Test patch:
diff --git a/tests/Dialect/KeywordsDialectProviderTest.php b/tests/Dialect/KeywordsDialectProviderTest.php
index 3d638593..5cef735e 100644
--- a/tests/Dialect/KeywordsDialectProviderTest.php
+++ b/tests/Dialect/KeywordsDialectProviderTest.php
@@ -12,6 +12,7 @@
 
 use Behat\Gherkin\Dialect\KeywordsDialectProvider;
 use Behat\Gherkin\Keywords\ArrayKeywords;
+use Behat\Gherkin\Keywords\CachedArrayKeywords;
 use PHPUnit\Framework\TestCase;
 
 class KeywordsDialectProviderTest extends TestCase
@@ -40,4 +41,15 @@ public function testFailsForEmptyKeywordString(): void
         $this->expectExceptionMessage('A keyword string must contain at least one keyword.');
         $dialectProvider->getDialect('en');
     }
+
+    public function testDefaultDialectAfterExplicitDialect(): void
+    {
+        $dialectProvider = new KeywordsDialectProvider(CachedArrayKeywords::withDefaultKeywords());
+
+        $ruDialect = $dialectProvider->getDialect('ru');
+        $defaultDialect = $dialectProvider->getDefaultDialect();
+
+        $this->assertSame('ru', $ruDialect->getLanguage());
+        $this->assertSame('en', $defaultDialect->getLanguage());
+    }
 }