Skip to content

Instantly share code, notes, and snippets.

@SantoJambit
Created August 3, 2021 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SantoJambit/2fdcf696645dc7ef72ca0e70ebbd7b4a to your computer and use it in GitHub Desktop.
Save SantoJambit/2fdcf696645dc7ef72ca0e70ebbd7b4a to your computer and use it in GitHub Desktop.
nginx accept-language redirect by using NJS

Let's say you have an nginx set up to serve static files in a localized manner (for example HTML exported by next.js).

Your paths may look like this:

/de-DE
/en-US
/de-DE/about-us
/en-US/about-us
...

Now, if users reach the root of your domain, you want them to be redirected to the best matching language-specific path. There is a module, which you can re-compile nginx with to get support for this, but it completely ignores the quality setting: https://www.nginx.com/resources/wiki/modules/accept_language/

An alternative is to make the redirect using nginx's support for JavaScript.

Here's what the JavaScript for this might look like:

var defaultLocale = "en-US";
var locales = ["en-US", "de-DE"];

function getBestLocale(acceptLanguage) {
    if (!acceptLanguage || !acceptLanguage.trim()) {
        return defaultLocale;
    }

    var localeSearch = locales.map((locale) => ({ locale, lowerLocale: locale.toLowerCase() }));
    var bestLocale = defaultLocale;
    var bestQuality = -1;

    // Example (q is 1 by default, but can be set manually): en-US,en;q=0.5,de;q=0.9
    var acceptLanguageParts = acceptLanguage.toLowerCase().split(",");
    for (var i = 0; i < acceptLanguageParts.length; i++) {
        var parts = acceptLanguageParts[i].split(";");
        var locale = parts.shift();
        var q = parts.find((part) => part.startsWith("q="));

        var quality = q ? parseFloat(q.substr(2)) : 1;

        if (quality > bestQuality) {
            // Check for exact match first
            var match = localeSearch.find((l) => l.lowerLocale === locale);
            if (match) {
                bestLocale = match.locale;
                bestQuality = quality;
                continue;
            }

            // Check for for simple prefix match (for example if locale="en", test for "en-")
            var prefix = locale + "-";
            match = localeSearch.find((l) => l.lowerLocale.startsWith(prefix));
            if (match) {
                bestLocale = match.locale;
                bestQuality = quality;
                continue;
            }

            // Check for for less ideal prefix match (for example if locale="en-US", test for "en-")
            prefix = locale.split("-")[0] + "-";
            match = localeSearch.find((l) => l.lowerLocale.startsWith(prefix));
            if (match) {
                bestLocale = match.locale;
                bestQuality = quality;
                continue;
            }
        }
    }

    return bestLocale;
}

function localeRedirect(r) {
    var locale = getBestLocale(r.headersIn["Accept-Language"]);
    r.headersOut["Location"] = "/" + locale + "/";
    r.status = 302;
    r.sendHeader();
    r.finish();
}

export default { localeRedirect };

Now, all you need to do is adjust your nginx configuration to load and execute this script:

js_import /etc/nginx/conf.d/nginxLib.js;

server {
    # ...

    location = / {
        js_content nginxLib.localeRedirect;
    }

    # ...
}

Also make sure that you have loaded the JavaScript module in your root nginx.conf:

load_module modules/ngx_http_js_module.so;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment