diff --git a/bower.json b/bower.json index d60f85d489..46a4bbc841 100644 --- a/bower.json +++ b/bower.json @@ -30,7 +30,7 @@ "bootstrap": "~3.1.0", "bootstrap-growl": "git://github.com/ifightcrime/bootstrap-growl.git#master", "bootstrap-tour": "~0.8.1", - "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git#develop", + "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared.git", "BrowserQuest": "git://github.com/mozilla/BrowserQuest.git", "github-buttons": "git://github.com/mdo/github-buttons.git", "marked": "~0.2.9", diff --git a/package.json b/package.json index d020f7bedd..8c063f0131 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0-152", "main": "./src/server.js", "dependencies": { - "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#develop", + "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared", "connect-mongo": "*", "express": "3.5.x", "gzippo": "*", diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index c0ba02c5a1..898f0a0d8f 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -213,7 +213,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' "public": Groups.publicGuilds() } $scope.type = 'guild'; - $scope.text = 'Guild'; + $scope.text = window.env.t('guild'); var newGroup = function(){ return new Groups.Group({type:'guild', privacy:'private'}); } @@ -316,7 +316,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' .controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', 'Challenges', '$state', '$compile', function($rootScope,$scope, Groups, User, Challenges, $state, $compile) { $scope.type = 'party'; - $scope.text = 'Party'; + $scope.text = window.env.t('party'); $scope.group = $rootScope.party = Groups.party(); $scope.newGroup = new Groups.Group({type:'party'}); diff --git a/public/js/controllers/inventoryCtrl.js b/public/js/controllers/inventoryCtrl.js index 7f8b51da1e..ca3740f872 100644 --- a/public/js/controllers/inventoryCtrl.js +++ b/public/js/controllers/inventoryCtrl.js @@ -104,7 +104,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User', var food = $scope.selectedFood if (food.key == 'Saddle') { if (!$window.confirm(window.env.t('useSaddle', {pet: pet}))) return; - } else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text}))) { + } else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text()}))) { return; } User.user.ops.feed({params:{pet: pet, food: food.key}}); @@ -124,7 +124,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', '$window', 'User', var item = Content.quests[quest]; var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]); if (!completedPrevious) - return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text})); + return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text()})); if (item.lvl && item.lvl > user.stats.lvl) return alert(window.env.t('mustLevel', {level: item.lvl})); $rootScope.selectedQuest = item; diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 924d0afe5a..5ecce87aa2 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -204,11 +204,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ $http.post('/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId) .success(function(){ - var msg = window.env.t('youCast', {spell: spell.text}); + var msg = window.env.t('youCast', {spell: spell.text()}); switch (type) { - case 'task': msg = window.env.t('youCastTarget', {spell: spell.text, target: target.text});break; - case 'user': msg = window.env.t('youCastTarget', {spell: spell.text, target: target.profile.name});break; - case 'party': msg = window.env.t('youCastParty', {spell: spell.text});break; + case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break; + case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break; + case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break; } Notification.text(msg); }); diff --git a/public/js/env.js b/public/js/env.js index 8a6611cd90..bcae78640c 100644 --- a/public/js/env.js +++ b/public/js/env.js @@ -12,9 +12,5 @@ if(window.moment && window.env.language && window.env.language.momentLang && win window.moment.lang(window.env.language.momentLangCode); } -window.env.t = function(stringName, vars){ - var string = window.env.translations[stringName]; - if(!string) return window._.template(window.env.translations.stringNotFound, {string: stringName}); - - return vars === undefined ? string : window._.template(string, vars); -} \ No newline at end of file +window.habitrpgShared.i18n.strings = window.env.translations; +window.env.t = window.habitrpgShared.i18n.t; \ No newline at end of file diff --git a/public/js/services/guideServices.js b/public/js/services/guideServices.js index 98879e040b..bd59b4ea4b 100644 --- a/public/js/services/guideServices.js +++ b/public/js/services/guideServices.js @@ -78,6 +78,13 @@ angular.module('guideServices', []). }); $('.main-herobox').popover('destroy'); var tour = new Tour({ + template: "
" + + "

" + + "
" + + "" + + "" + + "
" + + "
", onEnd: function(){ User.set({'flags.showTour': false}); } diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 1cde8de72d..a00912c70b 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -88,6 +88,8 @@ api.registerUser = function(req, res, next) { timestamps: {created: +new Date(), loggedIn: +new Date()} } }; + newUser.preferences = newUser.preferences || {}; + newUser.preferences.language = req.language; // User language detected from browser, not saved user = new User(newUser); // temporary for conventions @@ -255,7 +257,11 @@ api.setupPassport = function(router) { }, function(user, cb){ if (user) return cb(null, user); + user = new User({ + preferences: { + language: req.language // User language detected from browser, not saved + }, auth: { facebook: req.user, timestamps: {created: +new Date(), loggedIn: +new Date()} diff --git a/src/controllers/user.js b/src/controllers/user.js index e1d15f213a..3545d507b5 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -76,7 +76,7 @@ api.score = function(req, res, next) { if (task.type === 'daily' || task.type === 'todo') task.completed = direction === 'up'; } - var delta = user.ops.score({params:{id:task.id, direction:direction}}); + var delta = user.ops.score({params:{id:task.id, direction:direction}, language: req.language}); user.save(function(err,saved){ if (err) return next(err); @@ -336,7 +336,7 @@ api.cast = function(req, res, next) { if (group) { series.push(function(cb2){ - var message = '`'+user.profile.name+' casts '+spell.text + (targetType=='user' ? ' on '+found.profile.name : ' for the party')+'.`'; + var message = '`'+user.profile.name+' casts '+spell.text() + (targetType=='user' ? ' on '+found.profile.name : ' for the party')+'.`'; group.sendChat(message); group.save(cb2); }) @@ -397,6 +397,8 @@ api.batchUpdate = function(req, res, next) { res.locals.ops = []; var ops = _.transform(req.body, function(m,_req){ if (_.isEmpty(_req)) return; + _req.language = req.language; + m.push(function() { var cb = arguments[arguments.length-1]; res.locals.ops.push(_req); diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000000..5ea96380af --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,97 @@ +var fs = require('fs'), + path = require('path'), + _ = require('lodash'), + User = require('./models/user').model, + shared = require('habitrpg-shared'), + translations = {}; + +var loadTranslations = function(locale){ + var files = fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale)); + translations[locale] = {}; + _.each(files, function(file){ + _.merge(translations[locale], require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, file))); + }); +}; + +// First fetch english so we can merge with missing strings in other languages +loadTranslations('en'); + +fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/")).forEach(function(file) { + if(file === 'en' || file === 'README.md') return; + loadTranslations(file); + // Merge missing strings from english + _.defaults(translations[file], translations.en); +}); + +var langCodes = Object.keys(translations); + +var avalaibleLanguages = _.map(langCodes, function(langCode){ + return { + code: langCode, + name: translations[langCode].languageName + } +}); + +// Load MomentJS localization files +var momentLangs = {}; + +// Handle different language codes from MomentJS and /locales +var momentLangsMapping = { + 'en': 'en-gb', + 'no': 'nn' +}; + +var momentLangs = {}; + +_.each(langCodes, function(code){ + var lang = _.find(avalaibleLanguages, {code: code}); + lang.momentLangCode = (momentLangsMapping[code] || code); + try{ + // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files + var f = fs.readFileSync(path.join(__dirname, '/../node_modules/moment/lang/' + lang.momentLangCode + '.js'), 'utf8'); + momentLangs[code] = f; + }catch (e){} +}); + +var getUserLanguage = function(req, res, next){ + var getFromBrowser = function(){ + var acceptable = _(req.acceptedLanguages).map(function(lang){ + return lang.slice(0, 2); + }).uniq().value(); + var matches = _.intersection(acceptable, langCodes); + return matches.length > 0 ? matches[0] : 'en'; + }; + + var getFromUser = function(user){ + var lang; + if(user && user.preferences.language && translations[user.preferences.language]){ + lang = user.preferences.language; + }else{ + var preferred = getFromBrowser(); + lang = translations[preferred] ? preferred : 'en'; + } + req.language = lang; + next(); + }; + + if(req.locals && req.locals.user){ + getFromUser(req.locals.user); + }else if(req.session && req.session.userId){ + User.findOne({_id: req.session.userId}, function(err, user){ + if(err) return callback(err); + getFromUser(user); + }); + }else{ + getFromUser(null); + } +}; + +shared.i18n.translations = translations; + +module.exports = { + translations: translations, + avalaibleLanguages: avalaibleLanguages, + langCodes: langCodes, + getUserLanguage: getUserLanguage, + momentLangs: momentLangs +}; \ No newline at end of file diff --git a/src/middleware.js b/src/middleware.js index bb40be8334..79df5f8608 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -7,6 +7,8 @@ var limiter = require('connect-ratelimit'); var logging = require('./logging'); var domainMiddleware = require('domain-middleware'); var cluster = require('cluster'); +var i18n = require('./i18n.js'); +var shared = require('habitrpg-shared'); module.exports.apiThrottle = function(app) { if (nconf.get('NODE_ENV') !== 'production') return; @@ -139,121 +141,34 @@ var getManifestFiles = function(page){ return code; } -// Translations - -var translations = {}; - -var loadTranslations = function(locale){ - var files = fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale)); - translations[locale] = {}; - _.each(files, function(file){ - _.merge(translations[locale], require(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/", locale, file))); - }); -}; - -// First fetch english so we can merge with missing strings in other languages -loadTranslations('en'); - -fs.readdirSync(path.join(__dirname, "/../node_modules/habitrpg-shared/locales/")).forEach(function(file) { - if(file === 'en') return; - loadTranslations(file); - // Merge missing strings from english - _.defaults(translations[file], translations.en); -}); - -var langCodes = Object.keys(translations); - -var avalaibleLanguages = _.map(langCodes, function(langCode){ - return { - code: langCode, - name: translations[langCode].languageName - } -}); - -// Load MomentJS localization files -var momentLangs = {}; - -// Handle different language codes from MomentJS and /locales -var momentLangsMapping = { - 'en': 'en-gb', - 'no': 'nn' -}; - -var momentLangs = {}; - -_.each(langCodes, function(code){ - var lang = _.find(avalaibleLanguages, {code: code}); - lang.momentLangCode = (momentLangsMapping[code] || code); - try{ - // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files - var f = fs.readFileSync(path.join(__dirname, '/../node_modules/moment/lang/' + lang.momentLangCode + '.js'), 'utf8'); - momentLangs[code] = f; - }catch (e){} -}); - -var getUserLanguage = function(req, callback){ - var getFromBrowser = function(){ - var acceptable = _(req.acceptedLanguages).map(function(lang){ - return lang.slice(0, 2); - }).uniq().value(); - var matches = _.intersection(acceptable, langCodes); - return matches.length > 0 ? matches[0] : 'en'; - }; - - if(req.session && req.session.userId){ - User.findOne({_id: req.session.userId}, function(err, user){ - if(err) return callback(err); - if(user && user.preferences.language && translations[user.preferences.language]){ - return callback(null, _.find(avalaibleLanguages, {code: user.preferences.language})); - }else{ - var langCode = getFromBrowser(); - // Because english is usually always avalaible as an acceptable language for the browser, - // if the user visit the page when his own language is not avalaible yet - // he'll have english set in his preferences, which is not good. - //if(user && translations[langCode]){ - //user.preferences.language = langCode; - //user.save(); //callback? - //} - return callback(null, _.find(avalaibleLanguages, {code: langCode})) - } - }); - }else{ - return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()})); - } -} - module.exports.locals = function(req, res, next) { - getUserLanguage(req, function(err, language){ - if(err) return res.json(500, {err: err}); + var language = _.find(i18n.avalaibleLanguages, {code: req.language}); + var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' - var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' + // Load moment.js language file only when not on static pages + language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined); - // Load moment.js language file only when not on static pages - language.momentLang = ((!isStaticPage && momentLangs[language.code])|| undefined); + res.locals.habitrpg = { + NODE_ENV: nconf.get('NODE_ENV'), + BASE_URL: nconf.get('BASE_URL'), + GA_ID: nconf.get("GA_ID"), + IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), + STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), + getManifestFiles: getManifestFiles, + getBuildUrl: getBuildUrl, + avalaibleLanguages: i18n.avalaibleLanguages, + language: language, + isStaticPage: isStaticPage, + translations: i18n.translations[language.code], + t: function(){ // stringName and vars are the allowed parameters + var args = Array.prototype.slice.call(arguments, 0); + args.push(language.code); + return shared.i18n.t.apply(null, args); + }, + siteVersion: siteVersion + } - res.locals.habitrpg = { - NODE_ENV: nconf.get('NODE_ENV'), - BASE_URL: nconf.get('BASE_URL'), - GA_ID: nconf.get("GA_ID"), - IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), - STRIPE_PUB_KEY: nconf.get('STRIPE_PUB_KEY'), - getManifestFiles: getManifestFiles, - getBuildUrl: getBuildUrl, - avalaibleLanguages: avalaibleLanguages, - language: language, - isStaticPage: isStaticPage, - translations: translations[language.code], - t: function(stringName, vars){ - var string = translations[language.code][stringName]; - if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName}); - - return vars === undefined ? string : _.template(string, vars); - }, - siteVersion: siteVersion - } - - next(); - }); + next(); } module.exports.enTranslations = function(stringName, vars){ diff --git a/src/models/user.js b/src/models/user.js index 4e019af4aa..f261d72498 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -305,14 +305,14 @@ var UserSchema = new Schema({ id: { type: String, 'default': shared.uuid }, name: String, challenge: String - }], 'default': shared.content.userDefaults.tags}, + }]}, challenges: [{type: 'String', ref:'Challenge'}], - habits: {type:[TaskSchemas.HabitSchema], 'default': shared.content.userDefaults.habits}, - dailys: {type:[TaskSchemas.DailySchema], 'default': shared.content.userDefaults.dailys}, - todos: {type:[TaskSchemas.TodoSchema], 'default': shared.content.userDefaults.todos}, - rewards: {type:[TaskSchemas.RewardSchema], 'default': shared.content.userDefaults.rewards}, + habits: {type:[TaskSchemas.HabitSchema]}, + dailys: {type:[TaskSchemas.DailySchema]}, + todos: {type:[TaskSchemas.TodoSchema]}, + rewards: {type:[TaskSchemas.RewardSchema]}, extra: Schema.Types.Mixed @@ -348,6 +348,31 @@ UserSchema.post('init', function(doc){ UserSchema.pre('save', function(next) { + // Populate new users with default content + if (this.isNew){ + //TODO for some reason this doesn't work here: `_.merge(this, shared.content.userDefaults);` + var self = this; + _.each(['habits', 'dailys', 'todos', 'rewards', 'tags'], function(taskType){ + self[taskType] = _.map(shared.content.userDefaults[taskType], function(task){ + var newTask = task; + + // Render task's text and notes in user's language + if(taskType === 'tags'){ + // tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here + newTask.id = shared.uuid(); + newTask.name = task.name(self.preferences.language); + }else{ + newTask.text = task.text(self.preferences.language); + newTask.notes = task.notes(self.preferences.language); + } + + return newTask; + }); + }); + + this.preferences.language = undefined; + } + //this.markModified('tasks'); if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) { this.preferences.dayStart = 0; diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee index f78d8c5707..10fec102df 100644 --- a/src/routes/apiv2.coffee +++ b/src/routes/apiv2.coffee @@ -19,6 +19,7 @@ middleware = require("../middleware") cron = user.cron _ = require('lodash') content = require('habitrpg-shared').content +i18n = require('../i18n') module.exports = (swagger, v2) -> @@ -47,7 +48,7 @@ module.exports = (swagger, v2) -> spec: description: "Export user history" method: 'GET' - middleware: auth.auth + middleware: [auth.auth, i18n.getUserLanguage] action: dataexport.history #[todo] encode data output options in the data controller and use these to build routes # --------------------------------- @@ -138,7 +139,7 @@ module.exports = (swagger, v2) -> path("id", "Task ID", "string") query 'keep',"When unlinking a challenge task, how to handle the orphans?",'string',['keep','keep-all','remove','remove-all'] ] - middleware: auth.auth ## removing cron since they may want to remove task first + middleware: [auth.auth, i18n.getUserLanguage] ## removing cron since they may want to remove task first action: challenges.unlink @@ -227,7 +228,7 @@ module.exports = (swagger, v2) -> path: '/user' method: 'DELETE' description: "Delete a user object entirely, USE WITH CAUTION!" - middleware: auth.auth + middleware: [auth.auth, i18n.getUserLanguage] action: user["delete"] "/user/revive": @@ -306,7 +307,7 @@ module.exports = (swagger, v2) -> parameters:[ body '','The array of batch-operations to perform','object' ] - middleware: [middleware.forceRefresh, auth.auth, cron] + middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron] action: user.batchUpdate # Tags @@ -350,7 +351,7 @@ module.exports = (swagger, v2) -> parameters: [ query 'type',"Comma-separated types of groups to return, eg 'party,guilds,public,tavern'",'string' ] - middleware: auth.auth + middleware: [auth.auth, i18n.getUserLanguage] action: groups.list @@ -362,7 +363,7 @@ module.exports = (swagger, v2) -> parameters: [ body '','Group object (see GroupSchema)','object' ] - middleware: auth.auth + middleware: [auth.auth, i18n.getUserLanguage] action: groups.create "/groups/{gid}:GET": @@ -370,7 +371,7 @@ module.exports = (swagger, v2) -> path: '/groups/{gid}' description: "Get a group" parameters: [path('gid','Group ID','string')] - middleware: auth.auth + middleware: [auth.auth, i18n.getUserLanguage] action: groups.get "/groups/{gid}:POST": @@ -379,7 +380,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: "Edit a group" parameters: [body('','Group object (see GroupSchema)','object')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.update "/groups/{gid}/join": @@ -387,7 +388,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: 'Join a group' parameters: [path('gid','Id of the group to join','string')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.join "/groups/{gid}/leave": @@ -395,7 +396,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: 'Leave a group' parameters: [path('gid','ID of the group to leave','string')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.leave "/groups/{gid}/invite": @@ -406,7 +407,7 @@ module.exports = (swagger, v2) -> path 'gid','Group id','string' query 'uuid','User id to invite','string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action:groups.invite "/groups/{gid}/removeMember": @@ -417,7 +418,7 @@ module.exports = (swagger, v2) -> path 'gid','Group id','string' query 'uuid','User id to boot','string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action:groups.removeMember "/groups/{gid}/questAccept": @@ -428,7 +429,7 @@ module.exports = (swagger, v2) -> path 'gid',"Group id",'string' query 'key',"optional. if provided, trigger new invite, if not, accept existing invite",'string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action:groups.questAccept "/groups/{gid}/questReject": @@ -438,7 +439,7 @@ module.exports = (swagger, v2) -> parameters: [ path 'gid','Group id','string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.questReject "/groups/{gid}/questAbort": @@ -446,7 +447,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: 'Abort quest' parameters: [path('gid','Group to abort quest in','string')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.questAbort #TODO PUT /groups/:gid/chat/:messageId @@ -456,7 +457,7 @@ module.exports = (swagger, v2) -> path: "/groups/{gid}/chat" description: "Get all chat messages" parameters: [path('gid','Group to return the chat from ','string')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.getChat @@ -469,7 +470,7 @@ module.exports = (swagger, v2) -> query 'message', 'Chat message','string' path 'gid','Group id','string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.postChat # placing before route below, so that if !=='seen' it goes to next() @@ -480,7 +481,7 @@ module.exports = (swagger, v2) -> parameters: [ path 'gid','Group id','string' ] - middleware: [] + middleware: [i18n.getUserLanguage] action: groups.seenMessage "/groups/{gid}/chat/{messageId}": @@ -488,7 +489,7 @@ module.exports = (swagger, v2) -> method: 'DELETE' description: 'Delete a group' parameters: [path('gid','ID of group to delete','string')] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.deleteChatMessage "/groups/{gid}/chat/{mid}/like": @@ -499,7 +500,7 @@ module.exports = (swagger, v2) -> path 'gid','Group id','string' path 'mid','Message id','string' ] - middleware: [auth.auth, groups.attachGroup] + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup] action: groups.likeChatMessage # --------------------------------- @@ -514,19 +515,19 @@ module.exports = (swagger, v2) -> # --------------------------------- "/hall/heroes": spec: {} - middleware:[auth.auth] + middleware:[auth.auth, i18n.getUserLanguage] action: hall.getHeroes "/hall/heroes/{uid}:GET": spec: path: "/hall/heroes/{uid}" - middleware:[auth.auth, hall.ensureAdmin] + middleware:[auth.auth, i18n.getUserLanguage, hall.ensureAdmin] action: hall.getHero "/hall/heroes/{uid}:POST": spec: method: 'POST' path: "/hall/heroes/{uid}" - middleware: [auth.auth, hall.ensureAdmin] + middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin] action: hall.updateHero "/hall/patrons": @@ -534,7 +535,7 @@ module.exports = (swagger, v2) -> parameters: [ query 'page','Page number to fetch (this list is long)','string' ] - middleware:[auth.auth] + middleware:[auth.auth, i18n.getUserLanguage] action: hall.getPatrons @@ -549,7 +550,7 @@ module.exports = (swagger, v2) -> spec: path: '/challenges' description: "Get a list of challenges" - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.list @@ -559,7 +560,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: "Create a challenge" parameters: [body('','Challenge object (see ChallengeSchema)','object')] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.create "/challenges/{cid}:GET": @@ -584,7 +585,7 @@ module.exports = (swagger, v2) -> path 'cid','Challenge id','string' body('','Challenge object (see ChallengeSchema)','object') ] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.update "/challenges/{cid}:DELETE": @@ -593,7 +594,7 @@ module.exports = (swagger, v2) -> method: 'DELETE' description: "Delete a challenge" parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges["delete"] "/challenges/{cid}/close": @@ -604,7 +605,7 @@ module.exports = (swagger, v2) -> path 'cid','Challenge id','string' query 'uid','User ID of the winner','string',true ] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.selectWinner "/challenges/{cid}/join": @@ -612,7 +613,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: "Join a challenge" parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.join "/challenges/{cid}/leave": @@ -620,7 +621,7 @@ module.exports = (swagger, v2) -> method: 'POST' description: 'Leave a challenge' parameters: [path('cid','Challenge id','string')] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.leave "/challenges/{cid}/member/{uid}": @@ -630,7 +631,7 @@ module.exports = (swagger, v2) -> path 'cid','Challenge id','string' path 'uid','User id','string' ] - middleware: [auth.auth] + middleware: [auth.auth, i18n.getUserLanguage] action: challenges.getMember @@ -662,7 +663,7 @@ module.exports = (swagger, v2) -> #type: 'Pet' errorResponses: [] method: 'GET' - route.middleware ?= if path.indexOf('/user') is 0 then [auth.auth, cron] else [] + route.middleware ?= if path.indexOf('/user') is 0 then [auth.auth, i18n.getUserLanguage, cron] else [] swagger["add#{route.spec.method}"](route);true diff --git a/src/routes/auth.js b/src/routes/auth.js index 12b7769a41..edc54bc81d 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,17 +1,18 @@ var auth = require('../controllers/auth'); var express = require('express'); +var i18n = require('../i18n'); var router = new express.Router(); /* auth.auth*/ auth.setupPassport(router); //FIXME make this consistent with the others -router.post('/api/v2/register', auth.registerUser); -router.post('/api/v2/user/auth/local', auth.loginLocal); -router.post('/api/v2/user/auth/facebook', auth.loginFacebook); -router.post('/api/v2/user/reset-password', auth.resetPassword); -router.post('/api/v2/user/change-password', auth.auth, auth.changePassword); +router.post('/api/v2/register', i18n.getUserLanguage, auth.registerUser); +router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal); +router.post('/api/v2/user/auth/facebook', i18n.getUserLanguage, auth.loginFacebook); +router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword); +router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword); -router.post('/api/v1/register', auth.registerUser); -router.post('/api/v1/user/auth/local', auth.loginLocal); -router.post('/api/v1/user/auth/facebook', auth.loginFacebook); +router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); +router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); +router.post('/api/v1/user/auth/facebook', i18n.getUserLanguage, auth.loginFacebook); module.exports = router; \ No newline at end of file diff --git a/src/routes/dataexport.js b/src/routes/dataexport.js index b84fe09bd3..ad2e2797d0 100644 --- a/src/routes/dataexport.js +++ b/src/routes/dataexport.js @@ -3,10 +3,11 @@ var router = new express.Router(); var dataexport = require('../controllers/dataexport'); var auth = require('../controllers/auth'); var nconf = require('nconf'); +var i18n = require('../i18n') /* Data export */ -router.get('/history.csv',auth.authWithSession,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes -router.get('/userdata.xml',auth.authWithSession,dataexport.leanuser,dataexport.userdata.xml); -router.get('/userdata.json',auth.authWithSession,dataexport.leanuser,dataexport.userdata.json); +router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes +router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml); +router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json); module.exports = router; diff --git a/src/routes/pages.js b/src/routes/pages.js index 80e7be54e1..863139a60e 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -5,9 +5,10 @@ var _ = require('lodash'); var middleware = require('../middleware'); var user = require('../controllers/user'); var auth = require('../controllers/auth'); +var i18n = require('../i18n'); // -------- App -------- -router.get('/', middleware.locals, function(req, res) { +router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) { if (!req.headers['x-api-user'] && !req.headers['x-api-key'] && !(req.session && req.session.userId)) return res.redirect('/static/front') @@ -19,35 +20,35 @@ router.get('/', middleware.locals, function(req, res) { // -------- Marketing -------- -router.get('/static/front', middleware.locals, function(req, res) { +router.get('/static/front', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/front', {env: res.locals.habitrpg}); }); -router.get('/static/privacy', middleware.locals, function(req, res) { +router.get('/static/privacy', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/privacy', {env: res.locals.habitrpg}); }); -router.get('/static/terms', middleware.locals, function(req, res) { +router.get('/static/terms', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/terms', {env: res.locals.habitrpg}); }); -router.get('/static/api', middleware.locals, function(req, res) { +router.get('/static/api', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/api', {env: res.locals.habitrpg}); }); -router.get('/static/features', middleware.locals, function(req, res) { +router.get('/static/features', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/features', {env: res.locals.habitrpg}); }); -router.get('/static/videos', middleware.locals, function(req, res) { +router.get('/static/videos', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/videos', {env: res.locals.habitrpg}); }); -router.get('/static/contact', middleware.locals, function(req, res) { +router.get('/static/contact', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/contact', {env: res.locals.habitrpg}); }); -router.get('/static/plans', middleware.locals, function(req, res) { +router.get('/static/plans', i18n.getUserLanguage, middleware.locals, function(req, res) { res.render('static/plans', {env: res.locals.habitrpg}); }); diff --git a/src/server.js b/src/server.js index 205e379d6b..c494641a5f 100644 --- a/src/server.js +++ b/src/server.js @@ -27,6 +27,9 @@ if (cluster.isMaster && (isDev || isProd)) { var swagger = require("swagger-node-express"); var autoinc = require('mongoose-id-autoinc'); + // Setup translations + var i18n = require('./i18n'); + var middleware = require('./middleware'); var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; diff --git a/test/spec/rootCtrlSpec.js b/test/spec/rootCtrlSpec.js index 20bb41857f..52d31a1375 100644 --- a/test/spec/rootCtrlSpec.js +++ b/test/spec/rootCtrlSpec.js @@ -28,30 +28,4 @@ describe('Root Controller', function() { expect(scope.contribText({level: 8, text: 'Blacksmith'}, {npc: 'NPC'})).to.eql('NPC'); }); - describe('i18n', function(){ - var t = env.t; - beforeEach(function(){ - env.translations = { - "text1": 'translatedText1', - "text2": '<%= number %> eggs', - "stringNotFound" : "String '<%= string %>' not found.", - }; - }); - - it('translates strings', function(){ - expect(t('')).to.eql("String '' not found."); - expect(t('text1')).to.eql('translatedText1'); - expect(t(' text1 ')).to.eql("String ' text1 ' not found."); - expect(t('text3')).to.eql("String 'text3' not found."); - }); - - it('interpolates strings', function(){ - expect(t('text2', {number: 2})).to.eql('2 eggs'); - expect(t('text2', {number: 'ten'})).to.eql('ten eggs'); - expect(t('text2', {n: 10, number: 'ten'})).to.eql('ten eggs'); - expect(t('text2')).to.eql('<%= number %> eggs'); - }); - }); - - }); \ No newline at end of file diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index c004d1b9ef..28cf0b8b9c 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -4,9 +4,9 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html') .col-md-6.border-right h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('battleGearText'))=env.t('battleGear') li.customize-menu.inventory-gear - menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special", mystery:"Mystery"}', ng-show='gear[klass]') + menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery")}', ng-show='gear[klass]') div(ng-repeat='item in gear[klass]') - button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}') + button.customize-option(popover='{{item.notes()}}', popover-title='{{item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}') .col-md-6 h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('costumeText'))=env.t('costume') .checkbox.equipment-title @@ -15,9 +15,9 @@ script(type='text/ng-template', id='partials/options.inventory.equipment.html') |  =env.t('useCostume') li.customize-menu(ng-if='user.preferences.costume') - menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {warrior:"Warrior", wizard:"Mage", rogue:"Rogue", healer:"Healer", special:"Special", mystery:"Mystery"}', ng-show='gear[klass]') + menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery")}', ng-show='gear[klass]') div(ng-repeat='item in gear[klass]') - button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') + button.customize-option(popover='{{item.notes()}}', popover-title='{{item.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') script(type='text/ng-template', id='partials/options.inventory.drops.html') .container-fluid @@ -32,36 +32,36 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') p.muted(ng-show='eggCount < 1')=env.t('noEggs') div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)') //TODO move positioning this styling to css - button.customize-option(popover='{{Content.eggs[egg].notes}}', popover-title='{{Content.eggs[egg].text}} Egg', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !(user.items.pets[egg+"-"+selectedPotion.key]>0)}') + button.customize-option(popover='{{Content.eggs[egg].notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs[egg].text()}}"}), popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !(user.items.pets[egg+"-"+selectedPotion.key]>0)}') .badge.badge-info.stack-count {{points}} - //-p {{Content.eggs[egg].text}} + //-p {{Content.eggs[egg].text()}} li.customize-menu menu.hatchingPotion-menu(label=(env.t('hatchingPotions') + ' ({{potCount}})')) p.muted(ng-show='potCount < 1')=env.t('noHatchingPotions') div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)') - button.customize-option(popover='{{Content.hatchingPotions[pot].notes}}', popover-title='{{Content.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !(user.items.pets[selectedEgg.key+"-"+pot]>0)}') + button.customize-option(popover='{{Content.hatchingPotions[pot].notes()}}', popover-title!=env.t("potion", {potionType: "{{Content.hatchingPotions[pot].text()}}"}), popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !(user.items.pets[selectedEgg.key+"-"+pot]>0)}') .badge.badge-info.stack-count {{points}} li.customize-menu - menu.pets-menu(label=(env.t('scrolls') + ' ({{questCount}})')) + menu.pets-menu(label=(env.t('quests') + ' ({{questCount}})')) p.muted(ng-show='questCount < 1')=env.t('noScrolls') p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + '' div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]') - button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes | htmlDecode}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') + button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes() | htmlDecode}}", popover-title='{{quest.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') .badge.badge-info.stack-count {{points}} li.customize-menu menu.pets-menu(label=env.t('food') + ' ({{foodCount}})') p(ng-show='foodCount < 1')=env.t('noFood') div(ng-repeat='(food,points) in ownedItems(user.items.food)') - button.customize-option(popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{food}}') + button.customize-option(popover='{{Content.food[food].notes()}}', popover-title='{{Content.food[food].text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{food}}') .badge.badge-info.stack-count {{points}} li.customize-menu(ng-if='user.items.special.snowball') menu.pets-menu(label=env.t('special')) div - button.customize-option(popover='{{Content.special.snowball.notes}}', popover-title='{{Content.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball') + button.customize-option(popover='{{Content.special.snowball.notes()}}', popover-title='{{Content.special.snowball.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball') .badge.badge-info.stack-count {{user.items.special.snowball}} li.customize-menu(ng-if='user.items.special.valentineReceived[0]') @@ -91,29 +91,29 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') .popover-content p=env.t('welcomeMarket') p - button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedEgg.text}}", gold: "{{selectedEgg.value}}"}) - button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedPotion.text}}", gold: "{{selectedPotion.value}}"}) - button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text}}", gold: "{{selectedFood.value}}"}) + button.btn.btn-primary(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedEgg.text()}}", gold: "{{selectedEgg.value}}"}) + button.btn.btn-primary(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedPotion.text()}}", gold: "{{selectedPotion.value}}"}) + button.btn.btn-primary(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text()}}", gold: "{{selectedFood.value}}"}) menu.inventory-list(type='list') li.customize-menu menu.pets-menu(label=env.t('eggs')) div(ng-repeat='egg in Content.eggs', ng-if='egg.canBuy') - button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", egg)', class='Pet_Egg_{{egg.key}}') + button.customize-option(popover='{{egg.notes()}}', popover-title!=env.t("egg", {eggType: "{{egg.text()}}"}), popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", egg)', class='Pet_Egg_{{egg.key}}') p | {{egg.value}} span.Pet_Currency_Gem1x.inline-gems div(ng-show='user.achievements.quests.gryphon > 1') - button.customize-option(popover='{{Content.eggs.Gryphon.notes}}', popover-title='{{Content.eggs.Gryphon.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Gryphon)', class='Pet_Egg_Gryphon') + button.customize-option(popover='{{Content.eggs.Gryphon.notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs.Gryphon.text()}}"}), popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Gryphon)', class='Pet_Egg_Gryphon') p | {{Content.eggs.Gryphon.value}} span.Pet_Currency_Gem1x.inline-gems div(ng-show='user.achievements.quests.hedgehog > 1') - button.customize-option(popover='{{Content.eggs.Hedgehog.notes}}', popover-title='{{Content.eggs.Hedgehog.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Hedgehog)', class='Pet_Egg_Hedgehog') + button.customize-option(popover='{{Content.eggs.Hedgehog.notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs.Hedgehog.text()}}"}), popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Hedgehog)', class='Pet_Egg_Hedgehog') p | {{Content.eggs.Hedgehog.value}} span.Pet_Currency_Gem1x.inline-gems div(ng-show='user.achievements.quests.ghost_stag > 1') - button.customize-option(popover='{{Content.eggs.Deer.notes}}', popover-title='{{Content.eggs.Deer.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Deer)', class='Pet_Egg_Deer') + button.customize-option(popover='{{Content.eggs.Deer.notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs.Deer.text()}}"}), popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("eggs", Content.eggs.Deer)', class='Pet_Egg_Deer') p | {{Content.eggs.Deer.value}} span.Pet_Currency_Gem1x.inline-gems @@ -121,7 +121,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') li.customize-menu menu.pets-menu(label=env.t('hatchingPotions')) div(ng-repeat='pot in Content.hatchingPotions') - button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("hatchingPotions", pot)', class='Pet_HatchingPotion_{{pot.key}}') + button.customize-option(popover='{{pot.notes()}}', popover-title!=env.t("potion", {potionType: "{{pot.text()}}"}), popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("hatchingPotions", pot)', class='Pet_HatchingPotion_{{pot.key}}') p | {{pot.value}} span.Pet_Currency_Gem1x.inline-gems @@ -129,7 +129,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') li.customize-menu menu.pets-menu(label=env.t('food')) div(ng-repeat='food in Content.food', ng-if='food.canBuy') - button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.key}}') + button.customize-option(popover='{{food.notes()}}', popover-title='{{food.text()}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("food", food)', class='Pet_Food_{{food.key}}') p | {{food.value}} span.Pet_Currency_Gem1x.inline-gems @@ -138,7 +138,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') menu.pets-menu(label=env.t('quests')) p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + '' div(ng-repeat='quest in Content.quests', ng-if='quest.canBuy') - button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes | htmlDecode}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buyQuest(quest.key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') + button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes() | htmlDecode}}", popover-title='{{quest.text()}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buyQuest(quest.key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') p | {{quest.value}} span.Pet_Currency_Gem1x.inline-gems @@ -151,7 +151,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') | 4 span.Pet_Currency_Gem1x.inline-gems //-div - button.customize-option(popover='{{Content.spells.special.snowball.notes}}', popover-title='{{Content.spells.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball') + button.customize-option(popover='{{Content.spells.special.snowball.notes()}}', popover-title='{{Content.spells.special.snowball.text()}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball') p | {{Content.spells.special.snowball.value}} span.Pet_Currency_Gem1x.inline-gems diff --git a/views/options/inventory/stable.jade b/views/options/inventory/stable.jade index 32247a0b1b..52d5b16f15 100644 --- a/views/options/inventory/stable.jade +++ b/views/options/inventory/stable.jade @@ -15,7 +15,7 @@ script(type='text/ng-template', id='partials/options.inventory.mounts.html') menu.pets(type='list') li.customize-menu(ng-repeat='egg in Content.eggs') menu - div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.mountText}}', popover-placement='bottom', ng-init='mount = egg.key+"-"+potion.key') + div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover=env.t('mountName', {potion: '{{potion.text()}}', mount: '{{egg.mountText()}}'}), popover-placement='bottom', ng-init='mount = egg.key+"-"+potion.key') button(class="pet-button Mount_Head_{{mount}}", ng-show='user.items.mounts[mount]', ng-class='{active: user.items.currentMount == mount}', ng-click='chooseMount(egg.key, potion.key)') //div(class='Mount_Head_{{mount}}') button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[mount]') @@ -33,7 +33,7 @@ mixin petList(source) menu.pets(type='list') li.customize-menu(ng-repeat='egg in #{source}') menu - div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key') + div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover=env.t('petName', {potion: '{{potion.text()}}', egg: '{{egg.text()}}'}), popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key') button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)') .progress(ng-show='!user.items.mounts[pet] && egg.key!="Egg"') .progress-bar.progress-bar-success(style="width:{{user.items.pets[pet]/.5}}%") @@ -85,7 +85,7 @@ script(type='text/ng-template', id='partials/options.inventory.pets.html') li.customize-menu menu.pets-menu(label=env.t('food')) div(ng-repeat='(food,points) in ownedItems(user.items.food)') - button.customize-option(popover-append-to-body='true', popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}') + button.customize-option(popover-append-to-body='true', popover='{{Content.food[food].notes()}}', popover-title='{{Content.food[food].text()}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}') .badge.badge-info.stack-count {{points}} // Remove this once we have images in - p {{Content.food[food].text}} + p {{Content.food[food].text()}} diff --git a/views/options/profile.jade b/views/options/profile.jade index 0cf4158c43..b0f0d0bdcb 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -81,7 +81,7 @@ mixin customizeProfile(mobile) // Flower li.customize-menu - menu(label="Flower") + menu(label=env.t('flower')) button(class='head_0 customize-option', type='button', ng-click='set({"preferences.hair.flower":0})') each num in [1,2,3,4,5,6] button(class='hair_flower_#{num} customize-option', type='button', ng-click='set({"preferences.hair.flower":#{num}})') @@ -141,7 +141,7 @@ script(id='partials/options.profile.stats.html', type='text/ng-template') h3=env.t('characterBuild') h4 =env.t('class') + ': ' - span {{ {warrior:'Warrior',wizard:'Mage',healer:'Healer',rogue:'Rogue'}[user.stats.class] }}  + span {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[user.stats.class] }}  a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass') small.cost 3 table.table.table-striped diff --git a/views/options/social/challenge-box.jade b/views/options/social/challenge-box.jade index 65de163eb8..fa47595697 100644 --- a/views/options/social/challenge-box.jade +++ b/views/options/social/challenge-box.jade @@ -4,8 +4,7 @@ h3.panel-title=env.t('challenges')   a.pull-right(target='_blank', href='https://trello.com/card/challenges-individual-party-guild-public/50e5d3684fe3a7266b0036d6/58') - small - ="More info" + small=env.t('moreInfo') .panel-body.modal-fixed-height(bindonce='group.challenges') div(ng-if='group.challenges.length > 0') table.table.table-striped diff --git a/views/options/social/challenges.jade b/views/options/social/challenges.jade index aac9d759da..541c814358 100644 --- a/views/options/social/challenges.jade +++ b/views/options/social/challenges.jade @@ -107,7 +107,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') input.form-control(type='text', minlength="3", maxlength="16", ng-model='newChallenge.shortName', placeholder=env.t('challengeTag'), required)   a.hint.vertical-20(target='_blank', href='http://habitrpg.wikia.com/wiki/Tags', popover=env.t('challengeTagPop'), popover-trigger='mouseenter', popover-placement='right') - ="More info" + =env.t('moreInfo') .form-group textarea.form-control(cols='3', placeholder=env.t('challengeDescr'), ng-model='newChallenge.description') @@ -121,7 +121,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') span.input-suffix.Pet_Currency_Gem1x.inline-gems   span.hint.vertical-20(popover=env.t('prizePop'), popover-trigger='mouseenter', popover-placement='right') - ="More info" + =env.t('moreInfo') span(ng-show='newChallenge.group=="habitrpg"') !=env.t('publicChallenges') diff --git a/views/options/social/create-group.jade b/views/options/social/create-group.jade index bd2563cd76..6217bcb58c 100644 --- a/views/options/social/create-group.jade +++ b/views/options/social/create-group.jade @@ -2,10 +2,8 @@ .row form.col-md-12.form-horizontal(ng-submit='create(newGroup)') .form-group - label.control-label(for='new-group-name') - | {{text}}  - =env.t('name') - input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder=('{{text}} ' + env.t('name')), ng-model='newGroup.name') + label.control-label(for='new-group-name')=env.t('newGroupName', {groupType: "{{text}}"}) + input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder=env.t('newGroupName', {groupType: "{{text}}"}), ng-model='newGroup.name') .form-group label(for='new-group-description')=env.t('description') textarea.form-control#new-group-description.option-content(cols='3', placeholder=env.t('description'), ng-model='newGroup.description') diff --git a/views/options/social/group.jade b/views/options/social/group.jade index 6bc92d1ac6..96ed43d961 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -12,8 +12,8 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild // ------ Bosses ------- .panel.panel-default(bindonce='group', ng-if='group.type==="party" && group.quest.key') .panel-heading - h3.panel-title(ng-if='group.quest.active==false')=env.t('questInvitation') + ' {{Content.quests[group.quest.key].text}}' - h3.panel-title(ng-if='group.quest.active==true') {{Content.quests[group.quest.key].text}} + h3.panel-title(ng-if='group.quest.active==false')=env.t('questInvitation') + ' {{Content.quests[group.quest.key].text()}}' + h3.panel-title(ng-if='group.quest.active==true') {{Content.quests[group.quest.key].text()}} .panel-body.modal-fixed-height div(ng-if='group.quest.active==false') table.table.table-striped @@ -50,11 +50,11 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild tr(ng-repeat='(k,v) in group.quest.progress.collect', class='quest_collected_{{v >= Content.quests[group.quest.key].collect[k].count}}') td div.pull-left(class='quest_{{group.quest.key}}_{{k}}') - | {{Content.quests[group.quest.key].collect[k].text}} + | {{Content.quests[group.quest.key].collect[k].text()}} td {{v}} / {{Content.quests[group.quest.key].collect[k].count}} - div(ng-bind-html='Content.quests[group.quest.key].notes') + div(ng-bind-html='Content.quests[group.quest.key].notes()') hr h5=env.t('participants') table.table.table-striped diff --git a/views/options/social/index.jade b/views/options/social/index.jade index 570fcce153..99b4dfe27d 100644 --- a/views/options/social/index.jade +++ b/views/options/social/index.jade @@ -23,7 +23,9 @@ script(type='text/ng-template', id='partials/options.social.party.html') {{user.id}} p =env.t('startLFG') + |  a(href="http://habitrpg.wikia.com/wiki/Board:The_Archery_-_LFG")=env.t('linkLFG') + |  =env.t('endLFG') include ./create-group diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index 155a90d863..60f36bddf9 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -1,4 +1,3 @@ -//Why is shown? Because of script here at line 24!!!!!!!!! script(id='modals/buyGems.html', type='text/ng-template') .modal-body .buy-gems diff --git a/views/shared/modals/drops.jade b/views/shared/modals/drops.jade index fcb91e808a..7c12e4212b 100644 --- a/views/shared/modals/drops.jade +++ b/views/shared/modals/drops.jade @@ -5,7 +5,7 @@ script(type='text/ng-template', id='modals/dropsEnabled.html') p figure .item-drop-icon(class='Pet_Egg_Wolf') - span!=env.t('firstDrop', {eggText: "{{Content.eggs.Wolf.text}}", eggNotes: "{{Content.eggs.Wolf.notes}}"}) + span!=env.t('firstDrop', {eggText: "{{Content.eggs.Wolf.text()}}", eggNotes: "{{Content.eggs.Wolf.notes()}}"}) br p figure diff --git a/views/shared/modals/new-stuff.jade b/views/shared/modals/new-stuff.jade index e6b0422b40..400196574f 100644 --- a/views/shared/modals/new-stuff.jade +++ b/views/shared/modals/new-stuff.jade @@ -1,6 +1,6 @@ script(type='text/ng-template', id='modals/newStuff.html') .modal-header - h4 New Stuff! + h4=env.t('newStuff') .modal-body.new-stuff-modal.modal-fixed-height table tr @@ -723,5 +723,5 @@ script(type='text/ng-template', id='modals/newStuff.html') a(href='https://trello.com/card/custom-day-start/50e5d3684fe3a7266b0036d6/15') Add custom day start .modal-footer - button.btn.btn-default(ng-click='$close()') Cool - button.btn.btn-warning(ng-click='dismissAlert(); $close()') Dismiss This Alert + button.btn.btn-default(ng-click='$close()')=env.t('cool') + button.btn.btn-warning(ng-click='dismissAlert(); $close()')=env.t('dismissAlert') diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index 4d3a471efd..a34c96c5d7 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -3,7 +3,7 @@ script(id='partials/options.social.party.quest-rewards.html', type='text/ng-temp h5 {{header}} table.table.table-striped tr(ng-repeat='drop in quest.drop.items') - td {{drop.text}} + td {{drop.text()}} tr td {{quest.drop.exp}} =env.t('experience') @@ -13,20 +13,20 @@ script(id='partials/options.social.party.quest-rewards.html', type='text/ng-temp script(type='text/ng-template', id='modals/questCompleted.html') .modal-header - h4 "{{Content.quests[user.party.quest.completed].text}}" + h4 "{{Content.quests[user.party.quest.completed].text()}}" =env.t('completed') .modal-body - p {{Content.quests[user.party.quest.completed].completion}} + p {{Content.quests[user.party.quest.completed].completion()}} quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived')) .modal-footer button.btn.btn-primary(ng-click='set({"party.quest.completed":""}); $close()')=env.t('accept') script(type='text/ng-template', id='modals/showQuest.html') .modal-header - h4 {{selectedQuest.text}} + h4 {{selectedQuest.text()}} .modal-body .pull-right(class='quest_{{selectedQuest.key}}') - div(ng-bind-html='selectedQuest.notes') + div(ng-bind-html='selectedQuest.notes()') quest-rewards(key='{{selectedQuest.key}}') hr .npc_ian.pull-left @@ -38,10 +38,10 @@ script(type='text/ng-template', id='modals/showQuest.html') script(type='text/ng-template', id='modals/buyQuest.html') .modal-header - h4 {{selectedQuest.text}} + h4 {{selectedQuest.text()}} .modal-body .pull-right(class='quest_{{selectedQuest.key}}') - div(ng-bind-html='selectedQuest.notes') + div(ng-bind-html='selectedQuest.notes()') quest-rewards(key='{{selectedQuest.key}}') .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('neverMind') @@ -50,9 +50,9 @@ script(type='text/ng-template', id='modals/buyQuest.html') script(type='text/ng-template', id='modals/questInvitation.html') .modal-header h4=env.t('questInvitation') - | {{Content.quests[party.quest.key].text}} + | {{Content.quests[party.quest.key].text()}} .modal-body - div(ng-bind-html='Content.quests[party.quest.key].notes') + div(ng-bind-html='Content.quests[party.quest.key].notes()') quest-rewards(key='{{party.quest.key}}') .modal-footer button.btn.btn-default(ng-click='questHold = true; $close()')=env.t('askLater') diff --git a/views/shared/profiles/achievements.jade b/views/shared/profiles/achievements.jade index 85c5a41628..5b5243bdeb 100644 --- a/views/shared/profiles/achievements.jade +++ b/views/shared/profiles/achievements.jade @@ -28,6 +28,7 @@ div(ng-if='profile.contributor.level || user._id == profile._id') span.label.label-default(ng-if='!profile.contributor.level')=env.t('contribName') small =env.t('contribText') + |  a(href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG', target='_blank')=env.t('readMore') | . hr diff --git a/views/shared/profiles/stats.jade b/views/shared/profiles/stats.jade index b833f27368..2ae6ad7f85 100644 --- a/views/shared/profiles/stats.jade +++ b/views/shared/profiles/stats.jade @@ -22,12 +22,12 @@ table.table.table-striped(ng-show='user.flags.itemsEnabled') // FIXME this isn't refreshing properly. On first party modal, correct. Second party modal click gives the previous gear. It's always one behind. tr(ng-repeat='(k,v) in profile.items.gear.equipped', ng-init='piece=Content.gear.flat[v]', ng-show='piece') td - strong {{piece.text}}:  + strong {{piece.text()}}:  span(ng-repeat='stat in ["str","con","per","int"]', ng-show='piece[stat]') {{piece[stat]}} {{stat.toUpperCase()}}  h4=env.t('attributes') table.table.table-striped - tr(ng-repeat='(k,v) in {str:"Strength (STR)",int:"Intelligence (INT)",con:"Constitution (CON)",per:"Perception (PER)"}') + tr(ng-repeat='(k,v) in {str: env.t("strength") + " (STR)",int: env.t("intelligence") + " (INT)",con: env.t("constitution") + " (CON)",per:env.t("perception") + " (PER)"}') td span.hint(ng-if='k=="str"', popover-title=env.t('strength'), popover-placement='right', popover=env.t('strengthText'), popover-trigger='mouseenter', style='margin-right:3px') strong {{v}} diff --git a/views/shared/tasks/lists.jade b/views/shared/tasks/lists.jade index c2007e8a5b..9eb9f01b75 100644 --- a/views/shared/tasks/lists.jade +++ b/views/shared/tasks/lists.jade @@ -35,7 +35,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") // Static Rewards ul.items.rewards(ng-if='main && list.type=="reward" && user.flags.itemsEnabled') - li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.notes}}') + li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.notes()}}') // right-hand side control buttons .task-meta-controls span.task-notes @@ -47,12 +47,12 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span.shop_gold // main content span(bo-class='{"shop_{{item.key}} shop-sprite item-img": true}') - p.task-text {{item.text}} + p.task-text {{item.text()}} // Events ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball)') - li.task.reward-item(ng-if='user.items.special.snowball>0',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.snowball.notes}}') + li.task.reward-item(ng-if='user.items.special.snowball>0',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.snowball.notes()}}') .task-meta-controls span.task-notes span.glyphicon.glyphicon-comment @@ -62,9 +62,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span.reward-cost {{user.items.special.snowball}} span.shop_snowball(style='display:inline-block;vertical-align:top;') // main content - p.task-text {{Content.spells.special.snowball.text}} + p.task-text {{Content.spells.special.snowball.text()}} - li.task.reward-item(ng-if='user.stats.buffs.snowball',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.salt.notes}}') + li.task.reward-item(ng-if='user.stats.buffs.snowball',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.salt.notes()}}') .task-meta-controls span.task-notes span.glyphicon.glyphicon-comment @@ -75,11 +75,11 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") span.shop_gold // main content span.shop_salt.shop-sprite.item-img - p.task-text {{Content.spells.special.salt.text}} + p.task-text {{Content.spells.special.salt.text()}} // Spells ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses') - li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= spell.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{spell.notes}}') + li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= spell.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{spell.notes()}}') .task-meta-controls span.task-notes span.glyphicon.glyphicon-comment @@ -91,7 +91,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") =env.t('mp') // main content span(ng-class='{"shop_{{spell.key}} shop-sprite item-img": true}') - p.task-text {{spell.text}} + p.task-text {{spell.text()}} br diff --git a/views/static/features.jade b/views/static/features.jade index bb7ed4e76a..3a01e2ff24 100644 --- a/views/static/features.jade +++ b/views/static/features.jade @@ -11,76 +11,81 @@ block content .row#about-page(ng-controller='AboutCtrl') .col-md-12#aboutPage .marketing - h1 Improve Your Habits By Playing A Game + h1=env.t('marketing1Header') .row .col-md-6 - a.gallery(href='/marketing/screenshot.png', title='Improve Your Habits By Playing A Game') + a.gallery(href='/marketing/screenshot.png', title=env.t('marketing1Header')) img(src='/marketing/screenshot.png') - p.lead HabitRPG is a video game to help you improve real life habits. It "gamifies" your life by turning all your tasks (habits, dailies, and to-dos) into little monsters you have to conquor. The better you are at this, the more you progress in the game. If you slip up in life, your character starts backsliding in the game. + p.lead=env.t('marketing1Lead1') .col-md-6 - a.gallery(href='/marketing/gear.png', title='Get Sweet Gear') + a.gallery(href='/marketing/gear.png', title=env.t('marketing1Lead2Title')) img(src='/marketing/gear.png') - p.lead Get Sweet Gear. Improve your habits to build up your avatar. Show off the sweet gear you've earned + p.lead!=env.t('marketing1Lead2') - a.gallery(href='/marketing/drops.png', title='Find Random Prizes') + a.gallery(href='/marketing/drops.png', title=env.t('marketing1Lead3Title')) img(src='/marketing/drops.png',style='max-height:200px') - p.lead Find Random Prizes. For some, it's the gamble which motivates them, a system called "stochastic rewarding". HabitRPG accommodates all reinforcement styles: positive, negative, predictable, and random. + p.lead!=env.t('marketing1Lead3') // TODO achievements hr.clearfix - h1 Compete With Friends, Join Interest Groups + h1=env.t('marketing2Header') .row .col-md-6 - a.gallery(href='/marketing/guild.png', title='Compete With Friends, Join Interest Groups') + a.gallery(href='/marketing/guild.png', title=env.t('marketing2Header')) img(src='/marketing/guild.png') - p.lead While you can solo-play HabitRPG, the lights really turn on when you start collaborating, competing, and holding eachother accountable. The most effective part of any self-improvement program is social accountability, and what better an environment for accountability and competition than a video game? + p.lead=env.t('marketing2Lead1') - a.gallery(href='/bower_components/habitrpg-shared/img/sprites/spritesmith/quests/quest_vice3.png', title='Bosses') + a.gallery(href='/bower_components/habitrpg-shared/img/sprites/spritesmith/quests/quest_vice3.png', title=env.t('marketing2Lead2Title')) img(src='/bower_components/habitrpg-shared/img/sprites/spritesmith/quests/quest_vice3.png') - p.lead Fight Bosses. What's a Role Playing Game without battles? Fight bosses with your party. Bosses are "super accountability mode" - a day you miss the gym is a day the boss hurts everyone. + p.lead!=env.t('marketing2Lead2') .col-md-6 - a.gallery(href='/marketing/challenge.png', title='Challenges') + a.gallery(href='/marketing/challenge.png', title=env.t('challenges')) img(src='/marketing/challenge.png') - p.lead Challenges let you compete with friends and strangers. Whoever does the best at the end of a challenge wins special prizes. + p.lead!=env.t('marketing2Lead3') hr.clearfix - h1 Apps + h1=env.t('marketing3Header') .row .col-md-6 - a.gallery(href='/marketing/android_iphone.png', title='iPhone & Android') + a.gallery(href='/marketing/android_iphone.png', title=env.t('marketing3LeadTitle')) img(src='/marketing/android_iphone.png',style='box-shadow:none;') - p.lead The iPhone & Android apps let you take care of business on the go. We realize that logging into the website to click buttons can be a drag. + p.lead!=env.t('marketing3Lead1') .col-md-6 - a.gallery(href='/marketing/integration.png', title='iPhone & Android') + a.gallery(href='/marketing/integration.png', title=env.t('marketing3LeadTitle')) img(src='/marketing/integration.png') - p.lead Other 3rd Party Tools tie HabitRPG into various aspects of your life. Our API provides easy integration for things like the Chrome Extension, for which you lose points when browsing unproductive websites, and gain points when on productive ones. See more here + p.lead!=env.t('marketing3Lead2') hr.clearfix - h1 Organizational Use + h1=env.t('marketing4Header') .row .col-md-6 - h3 Gamification In Education + h3=env.t('marketing4Lead1Title') img.pull-left(src='/marketing/education.png') - p.lead Education is one of the best sectors for gamification. We all know how glued to phones and games students are these days, harness that power! Pit your students against eachother in friendly competition. Reward good behavior with rare prizes. Watch their grades and behavior soar. + p.lead=env.t('marketing4Lead1') .col-md-6 - h3 Gamification In Health and Wellness + h3=env.t('marketing4Lead2Title') img.pull-left(src='/marketing/wellness.png') - p.lead Health care costs are on the rise, and something's gotta give. Hundreds of programs are built to reduce costs and improve wellness. We believe HabitRPG can pave a substantial path towards healthy lifestyles. + p.lead=env.t('marketing4Lead2') .row .col-md-6.col-md-offset-3 - h3 Gamify Everything + h3=env.t('marketing4Lead3Title') img(src='/marketing/lefnire.png') p.lead - | Want to gamify your life?  - a.btn.btn-primary(ng-click='playButtonClick()') Play - | Interested in running a group in education, wellness, and more?  - a.btn.btn-primary(href='/static/plans',target='_blank') Contact Us - | Want to learn more?  - a.btn.btn-primary(href='/static/videos') Watch Videos + =env.t('marketing4Lead3-1') + |  + a.btn.btn-primary(ng-click='playButtonClick()')=env.t('playButton') + |  + =env.t('marketing4Lead3-2') + |  + a.btn.btn-primary(href='/static/plans',target='_blank')=env.t('contactUs') + |  + =env.t('marketing4Lead3-3') + |  + a.btn.btn-primary(href='/static/videos')=env.t('watchVideos') diff --git a/views/static/login-modal.jade b/views/static/login-modal.jade index 0201b35504..46c0ca5f51 100644 --- a/views/static/login-modal.jade +++ b/views/static/login-modal.jade @@ -6,7 +6,7 @@ script(id='modals/login.html', type='text/ng-template') a(href='/auth/facebook') img(src='/bower_components/habitrpg-shared/img/facebook-login-register.jpeg', alt=env.t('loginFacebookAlt')) //can we add in google auth? I like google auth - h3 Or + h3=env.t('or') ul.nav.nav-tabs li.active a(data-toggle='tab',data-target='#login-tab')=env.t('login')