<p>This can be done in 3 main steps.</p>



<p><strong>1. Get the site subdirectories working</strong></p>

<p>First just get the <code>/en/</code> and <code>/es/</code> subdirectory working:</p>

<ul>
<li>Create <code>en/</code> and <code>es/</code> subfolders in your webroot</li>
<li>Each subfolder should have its own <code>index.php</code> file and <code>.htaccess</code> file (if using Apache).</li>
<li><p>Assuming you used the <a href="https://github.com/craftcms/craft" rel="noreferrer">craftcms/craft</a> project as a starting point, update the <code>define('CRAFT_BASE_PATH'...</code> line in each of the <code>index.php</code> files to:</p>

<pre class="lang-php prettyprint-override"><code>define('CRAFT_BASE_PATH', dirname(__DIR__, 2));
</code></pre></li>
<li><p>Update your English site’s Base URL to include the <code>/en/</code>.</p></li>
</ul>

<p>At this point, if you point your browser directly to <code>/en/</code>, the English site should work as expected.</p>

<p><strong>2. Create a redirect script in the webroot’s index.php</strong></p>

<p>Create an <code>index.php</code> file in your webroot (alongside the <code>en/</code> and <code>es/</code> subfolders). This script will be responsible for redirecting the request to the correct subfolder, based on the requested URI and language.</p>

<pre class="lang-php prettyprint-override"><code>&lt;?php

// Bootstrap Craft -------------------------------------------------------------

// Set path constants
define('CRAFT_BASE_PATH', dirname(__DIR__));
define('CRAFT_VENDOR_PATH', CRAFT_BASE_PATH.'/vendor');

// Load Composer's autoloader
require_once CRAFT_VENDOR_PATH.'/autoload.php';

// Load dotenv?
if (file_exists(CRAFT_BASE_PATH.'/.env')) {
    (new Dotenv\Dotenv(CRAFT_BASE_PATH))-&gt;load();
}

// Load Craft (but don't run)
define('CRAFT_ENVIRONMENT', getenv('ENVIRONMENT') ?: 'production');
/** @var \craft\web\Application $app */
$app = require CRAFT_VENDOR_PATH.'/craftcms/cms/bootstrap/web.php';

// Custom Logic ----------------------------------------------------------------

// find a site language that matches the browser's requested language
$siteLanguages = $app-&gt;i18n-&gt;getSiteLocaleIds();
$language = $app-&gt;request-&gt;getPreferredLanguage($siteLanguages);

// find the first site that uses that language
foreach ($app-&gt;sites-&gt;getAllSites() as $site) {
    if ($site-&gt;language === $language) {
        // found it
        break;
    }
}

// figure out where to redirect this request now
$url = (function() use ($app, $site) {
    $uri = $app-&gt;request-&gt;getPathInfo();

    // see if we have an exact URI/language match
    if ($element = $app-&gt;elements-&gt;getElementByUri($uri, $site-&gt;id, true)) {
        return $element-&gt;getUrl();
    }

    // see if we have an exact URI match in any other site
    foreach ($app-&gt;sites-&gt;getAllSites() as $otherSite) {
        if ($otherSite !== $site) {
            if ($otherSiteElement = $app-&gt;elements-&gt;getElementByUri($uri, $otherSite-&gt;id, true)) {
                // found one! now see if the element is also available in the requested site
                if ($element = $otherSiteElement::find()-&gt;id($otherSiteElement-&gt;id)-&gt;siteId($site-&gt;id)-&gt;one()) {
                    return $element-&gt;getUrl();
                }
                // otherwise we'll just ignore the browser language
                return $otherSiteElement-&gt;getUrl();
            }
        }
    }

    // no element matches. we'll just redirect to the same URI in the requested site.
    return \craft\helpers\UrlHelper::siteUrl($uri, null, null, $site-&gt;id);
})();

// redirect to the URL
$app-&gt;response-&gt;redirect($url)-&gt;send();
</code></pre>

<p>With that in place, you should be able to start testing the redirects by pointing your browser to <code>/index.php?p=some/uri</code>.</p>

<p><strong>3. Redirect nonlocalized requests to the webroot’s index.php</strong></p>

<p>Finally, we want to get all requests that don’t start with <code>en/</code> or <code>es/</code> to be routed to an <code>index.php</code> file in your web root. If you’re using Apache, you can do that with <code>mod_rewrite</code>:</p>

<pre class="lang-php prettyprint-override"><code># Send requests missing en/ or es/ to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(en|es) [NC]
RewriteRule (.+) index.php?p=$1 [QSA,L]
</code></pre>

<p>Now everything should work!</p>

CC BY-SA 4.0
2021-03-01T21:48:38.630Z
Hi, I've tried this and get an error message: Fatal error: Uncaught TypeError: Argument 1 passed to Dotenv\Dotenv::__construct() must be an instance of Dotenv\Loader, string given. On line 14. Do you know why that could occur?
outline4
CC BY-SA 3.0
2018-02-06T13:26:46.457Z
2018-02-06T15:52:56.117Z
2018-02-06T15:52:56.117Z
Brandon Kelly
<p>I have setup a Craft 3 multi-site with the following configuration:</p>

<p><a href="https://i.sstatic.net/vR8NL.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/vR8NL.png" alt="enter image description here"></a></p>

<p>But what I really want is:</p>

<p><a href="https://i.sstatic.net/A3L1R.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/A3L1R.png" alt="enter image description here"></a></p>

<p>Additionally, I want to have a 'default' <strong><a href="http://localhost:8888" rel="nofollow noreferrer">http://localhost:8888</a></strong> so I can always <strong>redirect</strong> to /en or /es based on the browser language, but I don't want to add it as a site in the CMS through the admin panel, because I end up with a third unwanted version <strong>for each entry</strong>. </p>

<p>Which is the way to go?</p>

CC BY-SA 3.0
2018-02-05T20:08:06.423Z
Going with your 2nd image, you could just drop an `.htaccess` or `index.php` into whatever folder `http://localhost:8888` is configured to serve with your redirect logic in it, no?
Brad Bell
CC BY-SA 3.0
2018-02-05T21:08:09.513Z
@BradBell I have already unsuccessfully tried that but I may be doing something I shouldn't. My goal is to map my current `mysite.com` and all it's sections into `mysite.com/en` and `mysite.com/es`. Just like microsoft.com is doing; you can never browse `microsoft.com/section` but instead you get redirected to `microsoft.com/en-US/section`.
I got this to work only when I create a third Craft site: 
- default (.htaccess and index.php are under public/)
- English (.htaccess and index.php are under public/en)
- Spanish (they are under public/es)
Rosana Ruiz
CC BY-SA 3.0
2018-02-05T21:09:08.947Z
I want to be able to request mySite.com => Then I check craft.request and, based on browser language and other things, decide where I redirect. (I have this part working)
Rosana Ruiz
<localization><craft3><multi-site>
Craft 3 Multi-site with no "default" site
# Craft 3 Multi-site with no "default" site

(Asked by: username_0 on 2018-02-05)

I have setup a Craft 3 multi-site with the following configuration:

[![enter image description here](https://i.sstatic.net/vR8NL.png)](https://i.sstatic.net/vR8NL.png)

But what I really want is:

[![enter image description here](https://i.sstatic.net/A3L1R.png)](https://i.sstatic.net/A3L1R.png)

Additionally, I want to have a 'default' **<http://localhost:8888>** so I can always **redirect** to /en or /es based on the browser language, but I don't want to add it as a site in the CMS through the admin panel, because I end up with a third unwanted version **for each entry**.

Which is the way to go?

**Tags:** localization, craft3, multi-site
> **Comments:**
> **username_1**: Going with your 2nd image, you could just drop an `.htaccess` or `index.php` into whatever folder `http://localhost:8888` is configured to serve with your redirect logic in it, no?  
> **username_0**: @BradBell I have already unsuccessfully tried that but I may be doing something I shouldn't. My goal is to map my current `mysite.com` and all it's sections into `mysite.com/en` and `mysite.com/es`. Just like microsoft.com is doing; you can never browse `microsoft.com/section` but instead you get redirected to `microsoft.com/en-US/section`. I got this to work only when I create a third Craft site:  - default (.htaccess and index.php are under public/) - English (.htaccess and index.php are under public/en) - Spanish (they are under public/es)  
> **username_0**: I want to be able to request mySite.com => Then I check craft.request and, based on browser language and other things, decide where I redirect. (I have this part working)  

---

## 1 answers

### Answer by username_2 on 2018-02-06. Score: 6 | Accepted answer

This can be done in 3 main steps.
1. Get the site subdirectories working
First just get the /en/ and /es/ subdirectory working:

Create en/ and es/ subfolders in your webroot
Each subfolder should have its own index.php file and .htaccess file (if using Apache).
Assuming you used the craftcms/craft project as a starting point, update the define('CRAFT_BASE_PATH'... line in each of the index.php files to:
define('CRAFT_BASE_PATH', dirname(__DIR__, 2));

Update your English site’s Base URL to include the /en/.

At this point, if you point your browser directly to /en/, the English site should work as expected.
2. Create a redirect script in the webroot’s index.php
Create an index.php file in your webroot (alongside the en/ and es/ subfolders). This script will be responsible for redirecting the request to the correct subfolder, based on the requested URI and language.
load();
}

// Load Craft (but don't run)
define('CRAFT_ENVIRONMENT', getenv('ENVIRONMENT') ?: 'production');
/** @var \craft\web\Application $app */
$app = require CRAFT_VENDOR_PATH.'/craftcms/cms/bootstrap/web.php';

// Custom Logic ----------------------------------------------------------------

// find a site language that matches the browser's requested language
$siteLanguages = $app->i18n->getSiteLocaleIds();
$language = $app->request->getPreferredLanguage($siteLanguages);

// find the first site that uses that language
foreach ($app->sites->getAllSites() as $site) {
    if ($site->language === $language) {
        // found it
        break;
    }
}

// figure out where to redirect this request now
$url = (function() use ($app, $site) {
    $uri = $app->request->getPathInfo();

    // see if we have an exact URI/language match
    if ($element = $app->elements->getElementByUri($uri, $site->id, true)) {
        return $element->getUrl();
    }

    // see if we have an exact URI match in any other site
    foreach ($app->sites->getAllSites() as $otherSite) {
        if ($otherSite !== $site) {
            if ($otherSiteElement = $app->elements->getElementByUri($uri, $otherSite->id, true)) {
                // found one! now see if the element is also available in the requested site
                if ($element = $otherSiteElement::find()->id($otherSiteElement->id)->siteId($site->id)->one()) {
                    return $element->getUrl();
                }
                // otherwise we'll just ignore the browser language
                return $otherSiteElement->getUrl();
            }
        }
    }

    // no element matches. we'll just redirect to the same URI in the requested site.
    return \craft\helpers\UrlHelper::siteUrl($uri, null, null, $site->id);
})();

// redirect to the URL
$app->response->redirect($url)->send();

With that in place, you should be able to start testing the redirects by pointing your browser to /index.php?p=some/uri.
3. Redirect nonlocalized requests to the webroot’s index.php
Finally, we want to get all requests that don’t start with en/ or es/ to be routed to an index.php file in your web root. If you’re using Apache, you can do that with mod_rewrite:
# Send requests missing en/ or es/ to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(en|es) [NC]
RewriteRule (.+) index.php?p=$1 [QSA,L]

Now everything should work!

> **Comments:**
> **username_3**: Hi, I've tried this and get an error message: Fatal error: Uncaught TypeError: Argument 1 passed to Dotenv\Dotenv::__construct() must be an instance of Dotenv\Loader, string given. On line 14. Do you know why that could occur?  

---

