Issue with multi-language site using different domains

Hello!

I’m launching additional languages for my site - up until now, I had DE and EN, now I’m adding FR, IT, ES, PT and others.

Locally, everything works nice:

image

… but as soon as I upload it on the server (where I’m using escape-team.fr as the domain), I get 404 errors for all subpages, but the main site works:

https://www.escape-team.fr (working, including all assets)
Dossier de presse | Escape Team (not working for some reason)

This is my config.php:

<?php

function generateMissionsJson($langCode) {
    $data = [];
$missions = page('missions')->children()->listed();

        foreach ($missions as $mission) {

            if ($mission->missiontype() != "pack") {

            $mission = $mission->content($langCode);
            $missionData = [
                'id' => "mission".$mission->missionnumbernumeric(),
                'title' => ($mission->missiontitle_line1()->isNotEmpty() ? $mission->missiontitle_line1()->value() : ($mission->missiontitle()->isNotEmpty() ? $mission->missiontitle()->value() : '')),
                'subtitle' => t("mission", "Mission", $langCode)." ".$mission->missionnumbernumeric(),
                'difficulty' => intval($mission->missiondifficulty()->isNotEmpty() ? $mission->missiondifficulty()->value() : ''),
                'duration' => intval($mission->missionduration()->isNotEmpty() ? $mission->missionduration()->value() : ''),
                'players_min' => intval($mission->missionplayersmin()->isNotEmpty() ? $mission->missionplayersmin()->value() : ''),
                'players_max' => intval($mission->missionplayersmax()->isNotEmpty() ? $mission->missionplayersmax()->value() : ''),

// no subtitles for now.
        "intro_text" => "", //$mission->missionintrotext()->isNotEmpty() ? $mission->missionintrotext()->value() : '',
        "game_won_text" => "", //$mission->missionwontext()->isNotEmpty() ? $mission->missionwontext()->value() : '',
        "game_over_text" => "", //$mission->missiontimesuptext()->isNotEmpty() ? $mission->missiontimesuptext()->value() : '',

                'hint_purchases_allowed' => true,
                'hint_purchases_cost_per_hint' => 30000,
                "hint_after_milliseconds"=> 30000,



        "intro_mp3" => "",
        "won_mp3" => "",
        "gameover_mp3" => "",


                'phases' => []
            ];

            // Loop through phases
            foreach ($mission->phases()->toStructure() as $phase) {
                $phaseData = [
                    'id' => !empty($phase->id()) ? $phase->id() : '',

                    'link' => $phase->link()->isNotEmpty() ? $phase->link()->value() : '',
                    'mp3' => $phase->mp3()->isNotEmpty() ? $phase->mp3()->value() : '',
                    'pin' => $phase->pin()->isNotEmpty() ? $phase->pin()->value() : '',
                    'hints' => []
                ];

                // Loop through hints
                foreach ($phase->hints()->toStructure() as $hint) {
                    $phaseData['hints'][] = $hint->hint()->isNotEmpty() ? $hint->hint()->value() : '';
                }

                $missionData['phases'][] = $phaseData;
            }

            $data[] = $missionData;
        }
    }

    // Return the data as JSON
    return new Kirby\Http\Response(json_encode($data), 'application/json');
}





return [

    'smartypants' => true,
    'debug' => false,


    'date'  => [
      'handler' => 'strftime'
    ],

    'languages.detect' => false,
    'languages' => true,

    // Autoresize
    'medienbaecker.autoresize.maxWidth' => 1920,
    'mountbatt.deepl.config' => [
        'api_key' => 'b874dd01-48f3-1850-7f41-222600213bf1:fx',
        'api_url' => 'https://api-free.deepl.com/v2/translate'
    ],

    // Cachebuster
    'schnti.cachebuster.active' => false,

    // Commentions
    'sgkirby.commentions.templatesWithComments' => ['article'],
    'sgkirby.commentions.templatesWithWebmentions' => [''],
    'sgkirby.commentions.hideforms' => false, /* Comment form hide buttom, if true */
    'sgkirby.commentions.expand' => true, /* Webmention form hide buttom, if true */
    'sgkirby.commentions.allowlinks' => true, /* Allow links in comments, if true */
    'sgkirby.commentions.autolinks' => true,  /* Automatically recognize URLs in comments and turn them into links. Has no effect, if allowlinks is false. */
    'sgkirby.commentions.commentfields' => [
      'name' => true,  // include name field and mark as required
      'email'=> true,  // include email as optional field
      'website',       // include optional website field
    ],
    'sgkirby.commentions.secret' => '<YOUR-SECRET>', // A valid secret key must be at least 10 characters long and may NOT include any of the following: & % # + nor a space sign .

    // Srcset
    'thumbs' => [
      'quality' => '85',
      'srcsets' => [
        'default' => [    
          '800w' => [
          'width' => 800
        ], 
          '1024w' => [
            'width' => 1024
        ], 
          '1920w' => [
            'width' => 1920
        ], 
        ],
      ]
    ],

    // Robots  
    'bnomei.robots-txt.sitemap' => 'sitemap.xml',
    'bnomei.robots-txt.groups' => [ // array or callback
      '*' => [ // user-agent
          'disallow' => [
              '/kirby/',
              '/site/',
          ],
          'allow' => [
              '/media/'
          ]
      ]
    ],


  'routes' => array_map(function($langCode) {
    return [
        'pattern' => 'missions_' . $langCode . '.json',
        'action'  => function() use ($langCode) {
            return generateMissionsJson($langCode);
        }
    ];
}, ['de', 'en', 'zh', 'nl', 'es', 'fr', 'it', 'pt', 'ru'])

    // Support for language detect option
    // 'routes' => [
      // [
          // 'pattern' => '/',
          // 'action'  => function () {
              // $session = kirby()->session();
           //  
              // if ($session->get('languages.detect', false) === false && option('languages.detect') === true) {
                  // $session->set('languages.detect', true);
                 //  
                  // return kirby()
                      // ->response()
                      // ->redirect(kirby()->detectedLanguage()->url());
              // }
             //  
              // return page();
            // }
        // ]
    // ]
      
];

This is my fr.php (the same issue exists for IT and other languages):

<?php


$fr_root = 'https://www.escape-team.fr'; // default value

if (isset($_SERVER['HTTP_HOST'])) {
    if (($_SERVER['HTTP_HOST'] == "translate.escape-team.com") || ($_SERVER['HTTP_HOST'] == "localhost") || ($_SERVER['HTTP_HOST'] == "127.0.0.1")) {
        $fr_root = NULL;
    }
}


return [
    'code' => 'fr',
    'default' => false,
    'direction' => 'ltr',
    'locale' => [
        'LC_ALL' => 'fr_FR'
    ],
    'name' => 'French',
    'translations' => [

        'phase' => 'Phase',
        'minutes' => ' min.',
        'mission' => 'Mission',
        'printnow' => 'IMPRIMER MAINTENANT',
        'newsletter_notify1' => 'Vous souhaitez être informé(e) des nouvelles missions ?',
        'newsletter_notify2' => 'Pas de spam. Promis.',
        'subscribe' => 'S\'ABONNER',


    ],
    'url' => $fr_root
]; 

… and here’s my de.php, which is interesting, because it works for DE:

<?php


$de_root = 'https://www.escape-team.de'; // default value

if (isset($_SERVER['HTTP_HOST'])) {
    if (($_SERVER['HTTP_HOST'] == "translate.escape-team.com") || ($_SERVER['HTTP_HOST'] == "localhost") || ($_SERVER['HTTP_HOST'] == "127.0.0.1")) {
        $de_root = NULL;
    }
}


return [
    'code' => 'de',
    'default' => false,
    'direction' => 'ltr',
    'locale' => [
        'LC_ALL' => 'de_DE'
    ],
    'name' => 'Deutsch',
    'mission' => "Mission",
    'translations' => [
    'minutes' => ' Minuten',
    'phase' => 'Phase',
    'printnow' => 'JETZT AUSDRUCKEN',
        'newsletter_notify1' => 'Sollen wir euch bei neuen Missionen benachrichtigen?',
        'newsletter_notify2' => 'Kein Spam. Versprochen.',
        'subscribe' => 'ANMELDEN',
    ],
    'url' => $de_root,
    // test
];

Lastly, this is my .htaccess:

# Apache block users from browsing folders without a default document
<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>


<IfModule mod_headers.c>
    <FilesMatch "\.(mp3|php)$">
        Header set Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>


# Kirby .htaccess


<IfModule mod_headers.c>

# serve files as plain text if the actual content type is not known
# (hardens against attacks from malicious file uploads)
Header set Content-Type "text/plain" "expr=-z %{CONTENT_TYPE}"
Header set X-Content-Type-Options "nosniff"

</IfModule>

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://escape-team.com"
    Header set Access-Control-Allow-Methods "GET"
</IfModule>

# rewrite rules
<IfModule mod_rewrite.c>

# enable awesome urls. i.e.:
# http://yourdomain.com/about-us/team
RewriteEngine on

# make sure to set the RewriteBase correctly
# if you are running the site in a subfolder.
# Otherwise links or the entire site will break.
#
# If your homepage is http://yourdomain.com/mysite
# Set the RewriteBase to:
#
# RewriteBase /mysite



RewriteRule "^create$"  "/create.php" [PT]
RewriteRule ^/?create/([^/d]+)?$ create.php$1 [L,QSA]
RewriteRule ^/?play/([^/d]+)?$ play.php?c=$1 [L,QSA]


# In some environments it's necessary to
# set the RewriteBase to:
#
# RewriteBase /

# Cachebuster plugin
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)\.(\d+)\.(js|css)$ $1.$3 [L]

# block files and folders beginning with a dot, such as .git
# except for the .well-known folder, which is used for Let's Encrypt and security.txt
RewriteRule (^|/)\.(?!well-known\/) index.php [L]

# block text files in the content folder from being accessed directly
RewriteRule ^content/(.*)\.(txt|md|mdown)$ index.php [L]

# block all files in the site folder from being accessed directly
# except for requests to plugin assets files
RewriteRule ^site/(.*) index.php [L]

# Enable authentication header
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

# block direct access to kirby and the panel sources
RewriteRule ^kirby/(.*) index.php [L]

# make site links work
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php [L]

</IfModule>

# compress text file responses
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>







Any leads are appreciated! Thank you!

Are you sure mod_rewrite is working?
I mean even https://www.escape-team.fr/create doesn’t work, and that routing is done outside of Kirby.

Also the errors you’re getting come directly from Apache, suggesting that the request isn’t even being forwarded to Kirby or PHP.

You can test if mod_rewrite is working by removing the checking tags <IfModule mod_rewrite.c> and </IfModule> around the rules. If mod_rewrite is disabled, you’ll get some error 500 pages when trying to access anything.

What’s strange is that it works in German, maybe mod_rewrite is enabled or not based on virtual host?

Good question!

https://www.escape-team.fr/create

should rewrite to

(which works)

… but it doesn’t.

But the .htaccess should be doing this, as it is, I guess?

# Apache block users from browsing folders without a default document
<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>


<IfModule mod_headers.c>
    <FilesMatch "\.(mp3|php)$">
        Header set Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>


# Kirby .htaccess


<IfModule mod_headers.c>

# serve files as plain text if the actual content type is not known
# (hardens against attacks from malicious file uploads)
Header set Content-Type "text/plain" "expr=-z %{CONTENT_TYPE}"
Header set X-Content-Type-Options "nosniff"

</IfModule>

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://escape-team.com"
    Header set Access-Control-Allow-Methods "GET"
</IfModule>

# rewrite rules
<IfModule mod_rewrite.c>

# enable awesome urls. i.e.:
# http://yourdomain.com/about-us/team
RewriteEngine on

# make sure to set the RewriteBase correctly
# if you are running the site in a subfolder.
# Otherwise links or the entire site will break.
#
# If your homepage is http://yourdomain.com/mysite
# Set the RewriteBase to:
#
# RewriteBase /mysite



RewriteRule "^create$"  "/create.php" [PT]
RewriteRule ^/?create/([^/d]+)?$ create.php$1 [L,QSA]
RewriteRule ^/?play/([^/d]+)?$ play.php?c=$1 [L,QSA]


# In some environments it's necessary to
# set the RewriteBase to:
#
# RewriteBase /

# Cachebuster plugin
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)\.(\d+)\.(js|css)$ $1.$3 [L]

# block files and folders beginning with a dot, such as .git
# except for the .well-known folder, which is used for Let's Encrypt and security.txt
RewriteRule (^|/)\.(?!well-known\/) index.php [L]

# block text files in the content folder from being accessed directly
RewriteRule ^content/(.*)\.(txt|md|mdown)$ index.php [L]

# block all files in the site folder from being accessed directly
# except for requests to plugin assets files
RewriteRule ^site/(.*) index.php [L]

# Enable authentication header
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

# block direct access to kirby and the panel sources
RewriteRule ^kirby/(.*) index.php [L]

# make site links work
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php [L]

</IfModule>

# compress text file responses
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>



Interestingly, it does that for

  • but all domains are, according to their Apache configs, served from the same directory.

If you had any further ideas that’d be highly appreciated :slight_smile:

(I also put a temporary phpinfo() up there:

https://escape-team.fr/phpinfo.php

)

Got it to work!

My apache2 config for the new domains didn’t have

AllowOverride All

set as a directive - hence the ignoring of the RewriteRules.

Thanks for the very helpful hint! :slight_smile:

Was about to suggest AllowOverride. Happy it works now :slight_smile:

1 Like

Thank you! And if you want to play the game, feel free to send me an email and I’m happy to send you a free voucher code for all missions. More puzzles to solve :wink:

1 Like

I haven’t even looked at the website itself, just the http headers and phpinfo() :sweat_smile:
I’ll have a look at the game this evening :slight_smile: