Language code array has weird index on live server

Hi everyone :slight_smile:

I’m not sure if this is the right place for this issue, since it is plugin related. In fact, I already found a (temporary) solution. But maybe someone with a deeper understanding of PHP can just tell me what is going on, and tell me if my solution will break some day.

Okay, here we go: When using the excellent new Sitemapper plugin by Cre8tive, I realized that it was working on my local machine but throwing an error on my remote server. After some reverse engineering with my very bad PHP skills, I narrowed the problem down to this: The plugin tries to get a value from the index [0] of an array. And this index does not exist when running on my remote server.

I simplified the code a little bit to get a dump just of this exact array:

  <?php
  
  $langs = [];
  foreach (kirby()->languages() as $lang){
      if(!empty(F::modified($page->contentFile($lang->code())))) { $langs[] = $lang; }
  };
  
  foreach ($langs as $l) {
    dump($l->locale());
  }

This is the output on my local machine (where the plugin works)

Array
(
    [0] => de_DE
)
Array
(
    [0] => en_US
)

And this is the output on the remote machine (where it does not work, since the index is 6 and not 0, WTF?)

Array
(
    [6] => de_DE
)
Array
(
    [6] => en_US
)

I endet up, changing the 0 by a 6 in the plugin code that is trying to read the array and it works fine on the remote server. But this seems more like a hacky solution and I would love to understand whats going on. So maybe someone has a hint what could cause this weird difference.

Thank you very much in advance

Is there a public download for the

?

I cannot find it at https://getkirby.com/plugins.

Is it a Kirby 3 plugin, you have installed?

1 Like

What version of PHP is the real server running? It might be out of date. Atleast match it to the same as your local server, or ideally 7.2+ (the minimum for Kirby is currently 7.1)

If the issue persists, post again :slight_smile:

@luxlogica might be able to help (it’s his plugin)

2 Likes

The array keys are actually numeric constants for the different locale categories, if you set your locale like this:

   'locale' => [
        LC_ALL         => 'en_US.utf-8',
        LC_COLLATE     => 'en_US.utf-8',
        LC_MONETARY    => 'en_US.utf-8',
        LC_NUMERIC     => 'en_US.utf-8',
        LC_TIME        => 'en_US.utf-8',
        LC_MESSAGES    => 'en_US.utf-8',
        LC_CTYPE       => 'en_US.utf-8',

    ],

and do a

dump(kirby()->language()->locale());

You get an array like this:

Array
(
    [0] => en_US.utf-8
    [1] => en_US.utf-8
    [3] => en_US.utf-8
    [4] => en_US.utf-8
    [5] => en_US.utf-8
    [6] => en_US.utf-8
    [2] => en_US.utf-8
)

Not sure if the numeric constant can differ across systems, though. I think @lukasbestle knows more about this.

1 Like

@HeinerEF You can find the plugin here: https://gitlab.com/cre8ivclick/sitemapper

@ammon I think @texnixe has given us a good suggestion: in you language file, how does the ‘locale’ entry look? Does it have a single entry, like:

locale' => [
        'en-US'
    ]  

…or does it have multiple entries, like the example above?

1 Like

Then the dumped array should have more than one item, I guess. Single or multiple entries doesn’t really explain the different index numbers. I remember some discussion around this but can’t find it anywhere anymore, nor did googling uncover anything for me. But I know for sure that @lukasbestle can enlighten us.

1 Like

I think you’ve nailed it, @texnixe. The ‘$language->locale()’ function returns an array. The problem is, that when the user declares the locale with a single item, it returns just that (at index 0). When the user uses detailed locale settings, even if they only declare one item - like ‘LC_ALL’ - that single item is going to have an index which is not ‘0’.

So, I’ll change the code to do the following:

  • check the ‘locale’ array, and if there is only one item - regardless of the index - just use that
  • if there is more than one item, then we’ll do a check, in order of priority, for LC_ALL, LC_COLLATE, LC_CTYPE, etc. - first item found is used.
  • last, because these detailed setting are likely to have an extension (like “.utf8”), we’ll have to trap it and delete it, so we’re left with just the locale.

I’ll wait to hear back, and unless you have other suggestions, I’ll release a fix tomorrow. :+1:

1 Like

WOW! What an awesome community! I hardly ever create board accounts, but this is such a friendly and active place – thank you very much. Even though I did not come up with the solution, I feel like I’ve contributed a little piece to an awesome open source plugin, which is a great feeling.

I hope one day I will release my own plugin to give something back to the community. Thank you all for your investigation and special thanks to @luxlogica for the Sitemapper plugin!

1 Like

@ammon Glad to hear you’re enjoying Sitemapper!

I’ve just pushed the fix to the repository - please, when you can test it out, and let me know whether it fixes it for you.

1 Like

I tested it and it works like a charm now! Thank you for the fix :slight_smile:

I haven’t been able to verify this behavior myself, but it looks like the values of those constants can differ depending on the PHP version and the versions of the underlying libraries that were used when compiling PHP.

So you definitely shouldn‘t ever use the numeric values manually. PHP assumes that the user always uses the constants (where it doesn’t matter what the specific numeric value is).

Not quite. You can find the code that handles this case here.

Kirby uses the LC_ALL constant internally, but that one can be 0 or 6 depending on the system. If it‘s 0, the key gets swallowed when writing the language config to disk. That‘s a bit unfortunate but we unfortunately can‘t fix that in Kirby.

@luxlogica You could simplify your code by using $l->locale(LC_ALL). This will always give you the locale value itself, no matter how it‘s stored internally.

Thank you for the tip, @lukasbestle! So, does that mean that even if the user doesn’t explicitly declare ‘LC_ALL’ - e.g., they declare ‘LC_COLLATE’ and ‘LC_MONETARY’ - I can still just call 'locale(LC_ALL) and it will work?

No, that‘s a setup we don‘t support. LC_ALL always needs to be defined as we need that internally.

at https://www.php.net/manual/en/function.setlocale.php:

category is a named constant specifying the category of the functions affected by the locale setting:

  • LC_ALL for all of the below
  • LC_COLLATE for string comparison, see strcoll()
  • LC_CTYPE for character classification and conversion, for example strtoupper()
  • LC_MONETARY for localeconv()
  • LC_NUMERIC for decimal separator (See also localeconv())
  • LC_TIME for date and time formatting with strftime()
  • LC_MESSAGES for system responses (available if PHP was compiled with libintl )

@HeinerEF What exactly do you want to tell us with that quote from the PHP manual?

… means for my understanding, that this can be used to make one setting for all the others.

@lukasbestle I need some guidance: what should we do if the user defines LC_TIME and/or LC_MONETARY and/or others? - in these cases, LC_ALL is usually not defined.

Should we assume that the user wants the page’s locale to be the computer’s default - e.g., locale_get_default()? Or does Kirby make assumptions based on the language code/language file? Example: let’s say that a user does this in their pt.php (Portuguese) language file:

'locale' => [ LC_TIME => 'en_US.UTF-8', LC_MONETARY => 'pt_BR.UTF-8' ]

Should we assume the locale for the Kirby page is ‘pt_BR’ (assume a precedence), just use ‘pt’ (language code from filename), use ‘en_US’ (first declared value), or try the default language of the system (‘locale_get_default’)? How is Kirby handling this?..

I just want to make sure that whatever I implement will be in-line with the way Kirby handles things.

Actually, for the purposes of defining the language of the page within the sitemap - to put into the ‘hreflang’ attribute - the language code is sufficient, if no locale (containing laguage+region) is available. So, I’ll update the code to just check for LC_ALL (as per @lukasbestle suggestion), and if that is not present, then just use the language code directly.

That one took a little more research than I thought!

Do you know, that at Docs: “Detailed settings” of “locale” there is no “LC_ALL” setting?

Do you want to add there the hint from Docs: “Detailed locale settings” from “Introduction”:

The LC_ALL locale should always be set as it is used as a default locale internally in the Kirby core. Other locales can be set to override the default LC_ALL locale for specific use-cases.

As I wrote above, LC_ALL always needs to be defined, so the case you are describing is not supported.

That sounds like a practical solution. :slight_smile:

Your links both point to the same section in the docs (where I added the info box yesterday). Did I misunderstand your suggestion?