When developing a ColdBox REST API we wanted to add multilingual capacities. Coldbox has a nice module cbi18n for this.  When you set a new locale using

i18n.setfwLocale(newLocale);

this locale will usually be saved in a cookie and sent back with every request. But for our REST API we are looking for a more standard way of content-negotiation. We want to set an Accept-language header on every request and return a Content-Language header for one of our supported languages. The Accept-language can contain multiple choices, and each choice can have language, country and a q factor which specifies a preference. Example:

Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

In our case we are only serving two languages, English (en) and Dutch (nl) which is the default if no header is specified. So, in our header we need to find out which language (nl, en or something else) is requested and if it is not Dutch (nl) we serve English. In a later stage we might add German for some customers but for now these two languages are sufficient.

Coldbox interceptors are the perfect vehicle for this kind of logic. We define a LanguageInterceptor which has the following code:

// Language interceptor
component{
	property name = "i18n"				inject="i18n@cbi18n";
	
	void function configure(){
	}

	function preProcess( event, interceptData, buffer, rc, prc ) {
		var languageHeader =event.getHTTPHeader( "Accept-Language", "nl" );
		// get first language from comma separated list
		//cleanup for potential ;q weighting factor
		//plus cleanup for potential - dash separator
		var firstLanguage = listFirst(listFirst(listFirst(languageHeader),";"),"-");
		var newLocale = ( firstLanguage == "nl") ? "nl_NL" : "en_US";
		var contentLanguage = ( firstLanguage == 'nl') ? "nl-NL" : "en-US";
		i18n.setfwLocale(newLocale);
		event.setHTTPHeader( name = "Content-Language", value = contentLanguage );
	}
}

As you can see from the listfirst/listfirst/listfirst line, there is some parsing involved, because there might be multiple language entries separated by a comma. Each of these entries could have a q factor appended (after a ; character) and finally there might be a country designation after a . So finally we end up with nl, en or something else. Coldfusion locales are formattet as nl_NL or en_US for example, and in the Content Language returned everything should be formatted with a dash instead of an underscore.

Finally, don’t forget to register your interceptor in Coldbox.cfc or your ModuleConfig.cfc!