Skip to content

Improve detectedLanguage to differentiate between different territories (en_GB/en_US) #7178

@tobiasfabian

Description

@tobiasfabian

Description

I have a multi-language site:

  • German (code: de / locale: de_DE)
  • British English (code: en / locale: en_GB)
  • American English (code: en-us / locale: en_US)

languages.detect option is set to true

Expected behavior
US Americans (en_US) should be redirected to example.com/en-us.

But they are redirected to example.com/en because $kirby->detectedLanguage() just looks after the language (de or en) part of the visitor’s locale and ignores the territory (US or GB) part of the

Screenshots

To reproduce

  1. Set browser language to en_US
  2. Visit example.com
  3. You are redirected to example.com/en, but should be redirected to example.com/en-us

Your setup

Kirby Version
4.7.0 (also tested with 5.0.0-beta.6)

Console output

Your system (please complete the following information)

  • Device: MacBook
  • OS: macOS 15
  • Browser: Firefox
  • Version: 138

Additional context

Suggested Solution

Update $kirby->detectedLanguage().

https://github.com/getkirby/kirby/tree/4.7.0/src/Cms/App.php#L573

/**
 * Detect the preferred language from the visitor object
 */
public function detectedLanguage(): Language|null
{
	$languages = $this->languages();
	$visitor   = $this->visitor();

	foreach ($visitor->acceptedLanguages() as $acceptedLang) {
		// Find locale matches (e.g. en_GB => en_GB)
		$matchLocale = function ($language) use ($acceptedLang) {
			$languageLocale = $language->locale(LC_ALL);
			$acceptedLocale = $acceptedLang->locale();

			return Str::substr($languageLocale, 0, 5) === Str::substr($acceptedLocale, 0, 5);
		};

		// Find language matches (e.g. en_GB => en)
		$matchLanguage = function ($language) use ($acceptedLang) {
			$languageLocale = $language->locale(LC_ALL);
			$acceptedLocale = $acceptedLang->locale();

			return
				$languageLocale === $acceptedLocale ||
				$acceptedLocale === Str::substr($languageLocale, 0, 2);
		};

		if ($language = $languages->filter($matchLocale)?->first()) {
			return $language;
		}

		if ($language = $languages->filter($matchLanguage)?->first()) {
			return $language;
		}
	}

	foreach ($visitor->acceptedLanguages() as $acceptedLang) {
		if ($language = $languages->findBy('code', $acceptedLang->code())) {
			return $language;
		}
	}

	return $this->defaultLanguage();
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions