0

I recently created two folders containing index files with the respective language of the folders, pt (Brazil) & en (English).

When the user accesses the page, I use a control structure to check the user's browser language, if the browser language is pt, then I redirect the user to https://mywebsite.com/pt/index, otherwise, I redirect the user to https://mywebsite.com/en/index.

Maybe this idea that I had to translate, is not so professional, but okay, the point is that I would like to remove the path from the URL folders of the website, that is, I want it to appear https://mywebsite.com/index regardless if I redirected the user, do you understand? That is, if I redirect the user to https://mywebsite.com/en/index, I don't want /pt to be displayed, just /index.

Is it possible to do this using Nginx?

EDIT

Tree ( /var/www)

├── mywebsite.com
│   └── html
│       ├── 404.html
│       ├── css
│       │   ├── bootstrap.css
│       │   └── style.css
│       ├── index.php
│       ├── jquery
│       │   └── jquery.js
│       ├── js
│       │   ├── bootstrap.js
│       │   └── vue.js
│       ├── logout.php
│       ├── pt
│       │   ├── index.php
│       │   ├── logout.php
│       │   ├── signin.php
│       │   └── signup.php
│       ├── signin.php
│       ├── signup.php
└── html
    ├── index.html
    └── index.nginx-debian.html

index.php

<?php

$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);

if($lang == 'pt') {
    header('Location: pt/');
    return;
}

?>

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>English</title>
    </head>
    <body>
        <h1>Welcome! You're browsing the website with the English language.</h1>
    </body>
    </html>

pt/index.php

<?php


$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);

if(!$lang == 'pt') {
    header('Location: https://mywebsite.com');
    return;
}

?>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Português</title>
</head>
<body>
    <h1>Bem vindo! Você está navegando no website com o idioma Português.</h1>
</body>
</html>

Nginx ( /etc/nginx/sites-available/mywebsite )

server {

        root /var/www/mywebsite.com/html;
        try_files $uri $uri/ @extensionless-php;
        index index.php;

        server_name mywebsite.com www.mywebsite.com;

        error_page 403      http://mywebsite.com/forbidden.html;
        error_page 404 =301 http://mywebsite.com/404.html;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.4-fpm.sock;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                internal;
        }

        location @extensionless-php {
                rewrite ^(.*)$ $1.php last;
        }


    listen 443;
    ssl on;
    ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    access_log /var/log/wss-access-ssl.log;
    error_log /var/log/wss-error-ssl.log;


}

server {
    if ($host = www.mywebsite.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = mywebsite.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

        listen 80;
        listen [::]:80;

        server_name mywebsite.com www.mywebsite.com;
   return 404;  # managed by Certbot

The control structure I said is now visible in the code. Really, this is working, but I don't know if what I did is appropriate, if it is professional or not, if there are risks ...

So judging by the information now available, when the user enters the page, the script will check the language of the user's browser and if it is pt, it will redirect to the page in Portuguese, and how that page in Portuguese is inside a folder with the name pt, soon the name of this folder will appear in the URL

I would like to know if there is a way to hide the name of the folder?

Note:

Richard Smith said: The user needs the ability to choose/change the language. Before I did this to translate into the user's language, I used a Google Translate button that could translate the page into the language the user chose, however, I didn't think it was that cool, it was making the website slow to load, and most users in Brazil haven't even used that. So I thought about how to automatically translate the page into the user's browser language, and I did what is in the code above but I really don't know if it's the best one to do

Ivan Shatsky said: choose one of two different root folders at nginx config. I didn't quite understand what that means. Should I create another configuration file in /etc/nginx/sites-available?

  • 1
    Your question is off-topic in StackOverflow. You have a better chance to get it answered if you move it to [Unix & Linux](https://unix.stackexchange.com/tour) or to [Superuser](https://superuser.com). – accdias Jan 26 '21 at 20:00
  • @accdias Oh, thanks! –  Jan 26 '21 at 20:03
  • @accdias You are wrong, these questions belongs either to StackOverflow or ServerFault. This was discussed on meta several times ([1](https://meta.stackoverflow.com/questions/283033/are-htaccess-questions-ever-on-topic-at-so), [2](https://meta.stackoverflow.com/questions/262222/should-nginx-questions-be-on-stack-overflow)). – Ivan Shatsky Jan 26 '21 at 20:06
  • 1
    @AnneBatch Can you explain what do you mean by *I use a control structure to check the user's browser language*? What structure, at what web app layer? I think it should be possible to check `Accept-Language` header and choose one of two different root folders at nginx config, without any other layers involved. – Ivan Shatsky Jan 26 '21 at 20:15
  • @IvanShatsky, No, I'm not. Both threads you linked do not state that Nginx configuration problems are on-topic on a programming forum as SO. The first one isn't even related to Nginx at all since they are discussing Apache's `.htaccess` files and the second one is just an opinion from an user that isn't even active anymore. – accdias Jan 27 '21 at 01:32
  • For reference, check ["What topics can I ask about here?"](https://stackoverflow.com/help/on-topic) and pay special attention to item 8 of _"Some questions are still off-topic, even if they fit into one of the categories listed above:"_ – accdias Jan 27 '21 at 01:52
  • 1
    @AnneBatch The user needs the ability to choose/change the language. The browser they are using may not always reflect their preferred language. You could remove the language from the URL, but then you would need to add a cookie to save the user's preference. – Richard Smith Jan 27 '21 at 07:46
  • I edited my question, please check –  Jan 27 '21 at 20:35
  • 1
    Note that these are "directories" and "pathnames" and not the Windows concept of "folders" which isn't the same thing. – Rob Jan 28 '21 at 01:41

1 Answers1

0

No, it doesn't mean you need the second configuration file in any way.

First of all, your way of checking the Accept-Language HTTP header (documented in RFC 7231) is far from perfect. For example, it can be equal to de;q=0.9,pt;q=0.8,en;q=0.7. The first two characters is de, but the user still prefer Portuguese over the English. With PHP you have an ability to check all the preferred languages list and additionally check the weight of each language, but the nginx config isn't a programming language, so lets simplify things assuming the preferred languages list is sorted by the weights as most of modern browsers does. Assume the following tree structure:

mywebsite.com
└── html
    ├── 404.html
    ├── css
    │   ├── bootstrap.css
    │   └── style.css
    ├── jquery
    │   └── jquery.js
    ├── js
    │   ├── bootstrap.js
    │   └── vue.js
    ├── en
    │   ├── index.php
    │   ├── logout.php
    │   ├── signin.php
    │   └── signup.php
    └── pt
        ├── index.php
        ├── logout.php
        ├── signin.php
        └── signup.php

Now you can use a different root folder for the PHP files the following way:

map $http_accept_language $used_language {
    ~en.*pt(*SKIP)(*F)|pt  pt; # when "pt" substring present and not preceded with "en"
    ~pt.*en(*SKIP)(*F)|en  en; # when "en" substring present and not preceded with "pt"
    default                en; # or use "pt" if you want the Portuguese to be default
}

server {

        root /var/www/mywebsite.com/html;
        ...

        location ~ \.php$ {
                root /var/www/mywebsite.com/html/$used_language; # override root folder
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.4-fpm.sock;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                internal;
        }

        ...
}

(Regex provided by Wiktor Stribiżew)

The map block can be simplified to

map $http_accept_language $used_language {
    ~en.*pt(*SKIP)(*F)|pt  pt; # when "pt" substring present and not preceded with "en"
    default                en;
}

or

map $http_accept_language $used_language {
    ~pt.*en(*SKIP)(*F)|en  en; # when "en" substring present and not preceded with "pt"
    default                pt;
}

You can duplicate the whole tree structure (css, js, etc.) to the both en and pt folders and use dynamic root folder as global site root with the only one root directive, but if you have the localized content only within the PHP files, I don't think it makes sense.

An important caveat

Since now you are providing different content via same URLs, you should specify the Vary header to notify any caching layer between your server and visiting user that your content can vary upon the Accept-Language HTTP header value:

        location ~ \.php$ {
                add_header Vary "Accept-Language";
                ...
        }

Cookies

Now to the Richard Smith's suggestion. I'm completely agree that the user should be able to switch the language. Here is an example combined with the previous answer:

nginx config

map $http_accept_language $language_by_header {
    ~en.*pt(*SKIP)(*F)|pt  pt; # when "pt" substring present and not preceded with "en"
    ~pt.*en(*SKIP)(*F)|en  en; # when "en" substring present and not preceded with "pt"
    default                en; # or use "pt" if you want the Portuguese to be default
}

map $cookie_language $used_language {
    pt                     pt;
    en                     en;
    # detect language from "Accept-Language" header when no "language" cookie present
    default                $language_by_header;
}

server {

        root /var/www/mywebsite.com/html;
        ...

        location ~ \.php$ {
                root /var/www/mywebsite.com/html/$used_language; # override root folder
                add_header Vary "Accept-Language";
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.4-fpm.sock;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                internal;
        }

        ...
}

en/index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>English</title>
    <script type='text/javascript' src='/js/functions.js'></script>
</head>
<body>
    <h1>Welcome! You're browsing the website with the English language.</h1>
    <a onclick="setCookie('pt');" href="<?php echo $_SERVER['REQUEST_URI']; ?>">pt</a>
</body>
</html>

pt/index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Português</title>
    <script type='text/javascript' src='/js/functions.js'></script>
</head>
<body>
    <h1>Bem vindo! Você está navegando no website com o idioma Português.</h1>
    <a onclick="setCookie('en');" href="<?php echo $_SERVER['REQUEST_URI']; ?>">en</a>
</body>
</html>

js/functions.js

function setCookie(lc) {
    var today = new Date();
    // cookie expiry date - plus 30 days from today
    var expiry = new Date(today.getTime() + 30 * 24 * 3600 * 1000);
    document.cookie = 'language=' + lc + '; path=/; expires=' + expiry.toGMTString();
}

Of course this example needs some explanation, like the previous nginx config, but writing long explanations is a very time-consuming task for a non-native English speakers like me, and I really have a lack of time right now, try to understand whatever you can yourself, and if you'll need some additional explanations, ask me a question(s) in comments.

Community
  • 1
  • 1
Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37
  • Excellent explanation. I did as you explained, but unfortunately when I enter the website, no pages are accessible, they all return a 404 error (I have already restarted nginx post configuration) –  Jan 28 '21 at 12:56
  • Is something wrong with my [code](https://pastebin.com/qXZgF20C)? –  Jan 28 '21 at 13:08
  • @AnneBatch I test this config before posting the answer and it work as expected. Are you rearrange your files according to new tree structure? – Ivan Shatsky Jan 28 '21 at 14:15
  • Well, I just made the new tree structure. When I type mywebsite.com, the page returns a 404 error, but when I type mywebsite.com/index, the page works perfectly, and the language change setting also works. How can I solve this problem that the page returns me a 404 error? –  Jan 28 '21 at 15:28
  • @AnneBatch Can't it be a cached response? Can you try again from incognito window? I'll re-check the configuration in a several hours, didn't have time for that right now, sorry. – Ivan Shatsky Jan 28 '21 at 15:33
  • I fixed this by just putting `$used_language` at the root of the server block (line 17). Now I don't know if I did the right thing, when you have time, could you please check the [code](https://pastebin.com/tzqXB8F9) and tell me if I did it right? In fact, is line 29 really necessary? –  Jan 28 '21 at 15:46