Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"homepage": "https://github.com/aboutbits/internationalization#readme",
"devDependencies": {
"@types/node": "^14.11.5",
"@types/jest": "^26.0.9",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
Expand Down
18 changes: 15 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ Internationalization

[![npm version](https://badge.fury.io/js/%40aboutbits%2Finternationalization.svg)](https://badge.fury.io/js/%40aboutbits%2Finternationalization)

This package includes utilities for working with different languages in the browser.
This package includes utilities for working with different languages in the browser.
This package works for client side rendered applications as well as for server side rendered applications.

## Table of content

Expand All @@ -22,15 +23,26 @@ npm install @aboutbits/internationalization
Second, you can setup the tool with all supported languages and the fallback language and then detect the given browser language.

```js
import Internationalization from '@aboutbits/internationalization'
import { Internationalization } from '@aboutbits/internationalization'

let supportedLanguages = ['en', 'de', 'it']
let fallbackLanguage = 'en'

let i18n = new Internationalization(supportedLanguages, fallbackLanguage)

let language = i18n.detectBrowserLanguage()
// Fetches the language only from the user's browser
let browserLanguage = i18n.detectBrowserLanguage()

// First check the cookies to see if a language is set. If not it will look in the browser.
// You can also pass a requiest obejct with which the language can be recognized already during the server side rendering
let language = i18n.detectLanguage()

// Sets a cookie with the specified language.
// There exists also a static version of this method.
// Ex. Internationalization.setLanguage("de")
i18n.setLanguage("de")

console.log(browserLanguage)
console.log(language)
```

Expand Down
140 changes: 131 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,143 @@
import { IncomingMessage } from 'http'
import {
getCookieFromDocument,
canUseDOM,
setCookie,
getCookieFromRequest,
} from './utilities'

class Internationalization<T extends string> {
supportedLanguages: T[]
private readonly cookieName: string

supportedLanguages: Array<T>
fallbackLanguage: T

constructor(supportedLanguages: T[], fallbackLanguage: T) {
/**
* ClientInternationalization utility package for working with languages
*
* @param supportedLanguages { Array<string> } - List of all supported languages
* @param fallbackLanguage { string } - Fallback language if no language matches
* @param cookieName { string } - Optional: Set a preferred cookie name
*/
constructor(
supportedLanguages: Array<T>,
fallbackLanguage: T,
cookieName?: string
) {
this.supportedLanguages = supportedLanguages
this.fallbackLanguage = fallbackLanguage

this.cookieName = typeof cookieName === 'string' ? cookieName : 'language'
}

/**
* Detect the browser language of the client
* If no language was found or if none of the
* supported language matches a fallback language will be returned
*
* @return {string} - Detected language
*/
detectBrowserLanguage(): T {
const browserLanguage =
(navigator.languages && navigator.languages[0]) || navigator.language
// Check if the DOM is presented.
// If the DOM isn't accessible the website may be rendered on a server
if (canUseDOM()) {
// Get browser language
const browserLanguage =
(navigator.languages && navigator.languages[0]) || navigator.language

return (
this.supportedLanguages.find((lang) => lang === browserLanguage) ||
this.fallbackLanguage
)
}

// Return fallback language if no DOM was found
return this.fallbackLanguage
}

/**
* Save a language to a cookie
* If the set language isn't in the list of supported languages
* the fallback language will be returned
*
* @param language { string } - Language to set
*/
setLanguage(language: string): void {
if (canUseDOM()) {
const cookieLanguage =
this.supportedLanguages.find((lang) => lang === language) ||
this.fallbackLanguage

setCookie(this.cookieName, cookieLanguage)
}
}

/**
* Get the current language
* This method will first check if a language cookie is present.
* If no cookie was found the method will get the language form the browser
*
* For handling cookies on the server side pass the request object
*
* @param request { IncomingMessage } - Optional: Request object for accessing cookies over request headers
* @return { string } - Found language or fallback language
*/
detectLanguage(request?: IncomingMessage): T {
// Check if request is not undefined
if (request) {
// Try to get language form cookie
const cookieFromRequest = getCookieFromRequest(this.cookieName, request)

if (cookieFromRequest) {
return cookieFromRequest as T
}

// Get accept language from request header
const acceptLanguage = request.headers['accept-language']

if (acceptLanguage) {
// Extract the locale string and the priority from header string
const requestedLocales = acceptLanguage.split(',').map((part) => {
const [locale, priority] = part.trim().split(';q=')

return { locale, priority: parseInt(priority) }
})

// Get the potential locale from requested locales array
const potentialLocale = requestedLocales
.sort((a, b) => b.priority - a.priority)
.find(
({ locale }) =>
locale !== '*' &&
this.supportedLanguages.some((lang) => locale === lang)
)

// Check if the potential locale is not undefined else return the fallback language
return (potentialLocale
? potentialLocale.locale
: this.fallbackLanguage) as T
}
}

// Check for DOM
if (canUseDOM()) {
// Try to get language form cookie
const language = getCookieFromDocument(this.cookieName)

// If cookie is undefined get the language form the browser
if (language == undefined) {
return this.detectBrowserLanguage()
}

// Return found language or fallback language
return (
this.supportedLanguages.find((lang) => lang === language) ||
this.fallbackLanguage
)
}

return (
this.supportedLanguages.find((lang) => lang === browserLanguage) ||
this.fallbackLanguage
)
return this.fallbackLanguage
}
}

export { Internationalization as default }
export default Internationalization
68 changes: 68 additions & 0 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Regex based filter to get cookie values
* This includes the =
*
* @param name { string } - Filter keyword
*/
import { IncomingMessage } from 'http'

const getCookiePattern = (name: string): RegExp => RegExp(name + '=.[^;]*')

/**
* Get cookie from document (Local DOM)
*
* @param name { string } - Cookie name
*/
function getCookieFromDocument(name: string): string | undefined {
const pattern = getCookiePattern(name)
const matched = document.cookie.match(pattern)

if (!matched) {
return undefined
}

const cookie = matched[0].split('=')
return decodeURIComponent(cookie[1])
}

function getCookieFromRequest(
name: string,
request: IncomingMessage
): string | undefined {
if (request.headers && request.headers.cookie) {
const pattern = getCookiePattern(name)
const matched = request.headers.cookie.match(pattern)

if (!matched) {
return undefined
}

const cookie = matched[0].split('=')
return decodeURIComponent(cookie[1])
}
}

/**
* Set new cookie
*
* @param name { string } - Cookie name
* @param value { string } - Cookie value
*/
function setCookie(name: string, value: string): void {
document.cookie = name + '=' + value + ';path=/'
}

/**
* Check if the DOM is available
* if this function returns false, it means that the page is not rendered by the client.
* However, it might be pre-rendered for example on a server.
*/
function canUseDOM(): boolean {
return !!(
typeof window != 'undefined' &&
window.document &&
window.document.createElement
)
}

export { getCookieFromDocument, getCookieFromRequest, setCookie, canUseDOM }