Very late to the thread, but a technique I've used, pre-Angular, is to take advantage of JSON and the flexibility of JS to dynamically reference collection keys, and use inalienable facts of the environment (host server name, current browser language, etc.) as inputs to selectively discriminate/prefer suffixed key names within a JSON data structure.
This provides not merely deploy-environment context (per OP) but any arbitrary context (such as language) to provide i18n or any other variance required simultaneously, and (ideally) within a single configuration manifest, without duplication, and readably obvious.
IN ABOUT 10 LINES VANILLA JS
Overly-simplified but classic example: An API endpoint base URL in a JSON-formatted properties file that varies per environment where (natch) the host server will also vary:
...
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': 'https://www.productionwebsite.com:9090/api/res/'
},
...
A key to the discrimination function is simply the server hostname in the request.
This, naturally, can be combined with an additional key based on the user's language settings:
...
'app': {
'NAME': 'Ferry Reservations',
'NAME@fr': 'Réservations de ferry',
'NAME@de': 'Fähren Reservierungen'
},
...
The scope of the discrimination/preference can be confined to individual keys (as above) where the "base" key is only overwritten if there's a matching key+suffix for the inputs to the function -- or an entire structure, and that structure itself recursively parsed for matching discrimination/preference suffixes:
'help': {
'BLURB': 'This pre-production environment is not supported. Contact Development Team with questions.',
'PHONE': '808-867-5309',
'EMAIL': '[email protected]'
},
'[email protected]': {
'BLURB': 'Please contact Customer Service Center',
'BLURB@fr': 'S\'il vous plaît communiquer avec notre Centre de service à la clientèle',
'BLURB@de': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': '[email protected]'
},
SO, if a visiting user to the production website has German (de) language preference setting, the above configuration would collapse to:
'help': {
'BLURB': 'Bitte kontaktieren Sie unseren Kundendienst!!1!',
'PHONE': '1-800-CUS-TOMR',
'EMAIL': '[email protected]'
},
What does such a magical preference/discrimination JSON-rewriting function look like? Not much:
// prefer(object,suffix|[suffixes]) by/par/durch storsoc
// prefer({ a: 'apple', a@env: 'banana', b: 'carrot' },'env') -> { a: 'banana', b: 'carrot' }
function prefer(o,sufs) {
for (var key in o) {
if (!o.hasOwnProperty(key)) continue; // skip non-instance props
if(key.split('@')[1]) { // suffixed!
// replace root prop with the suffixed prop if among prefs
if(o[key] && sufs.indexOf(key.split('@')[1]) > -1) o[key.split('@')[0]] = JSON.parse(JSON.stringify(o[key]));
// and nuke the suffixed prop to tidy up
delete o[key];
// continue with root key ...
key = key.split('@')[0];
}
// ... in case it's a collection itself, recurse it!
if(o[key] && typeof o[key] === 'object') prefer(o[key],sufs);
};
};
In our implementations, which include Angular and pre-Angular websites, we simply bootstrap the configuration well ahead of other resource calls by placing the JSON within a self-executing JS closure, including the prefer() function, and fed basic properties of hostname and language-code (and accepts any additional arbitrary suffixes you might need):
(function(prefs){ var props = {
'svcs': {
'VER': '2.3',
'API@localhost': 'http://localhost:9090/',
'[email protected]': 'https://www.uat.productionwebsite.com:9090/res/',
'[email protected]': 'https://www.productionwebsite.com:9090/api/res/'
},
...
/* yadda yadda moar JSON und bisque */
function prefer(o,sufs) {
// body of prefer function, broken for e.g.
};
// convert string and comma-separated-string to array .. and process it
prefs = [].concat( ( prefs.split ? prefs.split(',') : prefs ) || []);
prefer(props,prefs);
window.app_props = JSON.parse(JSON.stringify(props));
})([location.hostname, ((window.navigator.userLanguage || window.navigator.language).split('-')[0]) ] );
A pre-Angular site would now have a collapsed (no @ suffixed keys) window.app_props to refer to.
An Angular site, as a bootstrap/init step, simply copies the dead-dropped props object into $rootScope, and (optionally) destroys it from global/window scope
app.constant('props',angular.copy(window.app_props || {})).run( function ($rootScope,props) { $rootScope.props = props; delete window.app_props;} );
to be subsequently injected into controllers:
app.controller('CtrlApp',function($log,props){ ... } );
or referred to from bindings in views:
<span>{{ props.help.blurb }} {{ props.help.email }}</span>
Caveats? The @ character is not valid JS/JSON variable/key naming, but so far accepted. If that's a deal-breaker, substitute for any convention you like, such as "__" (double underscore) as long as you stick to it.
The technique could be applied server-side, ported to Java or C# but your efficiency/compactness may vary.
Alternately, the function/convention could be part of your front-end compile script, so that the full gory all-environment/all-language JSON is never transmitted over the wire.
UPDATE
We've evolved usage of this technique to allow multiple suffixes to a key, to avoid being forced to use collections (you still can, as deeply as you want), and as well to honor the order of the preferred suffixes.
Example (also see working jsFiddle):
var o = { 'a':'apple', 'a@dev':'apple-dev', 'a@fr':'pomme',
'b':'banana', 'b@fr':'banane', 'b@dev&fr':'banane-dev',
'c':{ 'o':'c-dot-oh', 'o@fr':'c-point-oh' }, 'c@dev': { 'o':'c-dot-oh-dev', 'o@fr':'c-point-oh-dev' } };
/*1*/ prefer(o,'dev'); // { a:'apple-dev', b:'banana', c:{o:'c-dot-oh-dev'} }
/*2*/ prefer(o,'fr'); // { a:'pomme', b:'banane', c:{o:'c-point-oh'} }
/*3*/ prefer(o,'dev,fr'); // { a:'apple-dev', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*4*/ prefer(o,['fr','dev']); // { a:'pomme', b:'banane-dev', c:{o:'c-point-oh-dev'} }
/*5*/ prefer(o); // { a:'apple', b:'banana', c:{o:'c-dot-oh'} }
1/2 (basic usage) prefers '@dev' keys, discards all other suffixed keys
3 prefers '@dev' over '@fr', prefers '@dev&fr' over all others
4 (same as 3 but prefers '@fr' over '@dev')
5 no preferred suffixes, drops ALL suffixed properties
It accomplishes this by scoring each suffixed property and promoting the value of a suffixed property to the non-suffixed property when iterating over the properties and finding a higher-scored suffix.
Some efficiencies in this version, including removing dependence on JSON to deep-copy, and only recursing into objects that survive the scoring round at their depth:
function prefer(obj,suf) {
function pr(o,s) {
for (var p in o) {
if (!o.hasOwnProperty(p) || !p.split('@')[1] || p.split('@@')[1] ) continue; // ignore: proto-prop OR not-suffixed OR temp prop score
var b = p.split('@')[0]; // base prop name
if(!!!o['@@'+b]) o['@@'+b] = 0; // +score placeholder
var ps = p.split('@')[1].split('&'); // array of property suffixes
var sc = 0; var v = 0; // reset (running)score and value
while(ps.length) {
// suffix value: index(of found suffix in prefs)^10
v = Math.floor(Math.pow(10,s.indexOf(ps.pop())));
if(!v) { sc = 0; break; } // found suf NOT in prefs, zero score (delete later)
sc += v;
}
if(sc > o['@@'+b]) { o['@@'+b] = sc; o[b] = o[p]; } // hi-score! promote to base prop
delete o[p];
}
for (var p in o) if(p.split('@@')[1]) delete o[p]; // remove scores
for (var p in o) if(typeof o[p] === 'object') pr(o[p],s); // recurse surviving objs
}
if( typeof obj !== 'object' ) return; // validate
suf = ( (suf || suf === 0 ) && ( suf.length || suf === parseFloat(suf) ) ? suf.toString().split(',') : []); // array|string|number|comma-separated-string -> array-of-strings
pr(obj,suf.reverse());
}
'ngconstant:development'
in'serve'
- if you put it in watch's config under'gruntfile'
astasks: ['ngconstant:development']
- you won't need to restartgrunt serve
when you update the development variables in the gruntfile. – Ruthy