Merge pull request #317 from gconsidine/ui/angular-manual-init-for-locale

Implement manual initialzation for Angular app
This commit is contained in:
Greg Considine 2017-09-28 19:31:58 -04:00 committed by GitHub
commit 91bceefee7
17 changed files with 268 additions and 159 deletions

View File

@ -17,3 +17,4 @@ test
!client/lib/models/**/*.js
!client/lib/services/**/*.js
!client/features/**/*.js
!client/src/app.start.js

View File

@ -20,7 +20,7 @@ module.exports = {
node: true
},
globals: {
angular: true,
angular: true,
d3: true,
$: true,
_: true,
@ -43,6 +43,7 @@ module.exports = {
'no-param-reassign': 'off',
'no-plusplus': 'off',
'no-underscore-dangle': 'off',
'no-use-before-define': 'off',
'object-curly-newline': 'off',
'space-before-function-paren': ['error', 'always']
}

View File

@ -109,7 +109,7 @@ const base = {
jsonlint: 'codemirror.jsonlint'
}),
new ExtractTextPlugin('css/[name].[chunkhash].css'),
new CleanWebpackPlugin([STATIC_PATH, COVERAGE_PATH, LANGUAGES_PATH], {
new CleanWebpackPlugin([STATIC_PATH, COVERAGE_PATH], {
root: UI_PATH,
verbose: false
}),

View File

@ -1,5 +1,3 @@
const path = require('path');
const _ = require('lodash');
const base = require('./webpack.base');

View File

@ -1,13 +1,12 @@
const path = require('path');
const _ = require('lodash');
const webpack = require('webpack');
const STATIC_URL = '/static/';
const development = require('./webpack.development');
const development = require('./webpack.base');
const test = {
devtool: 'cheap-source-map',
plugins: [
new webpack.DefinePlugin({
$basePath: STATIC_URL

View File

@ -2,8 +2,8 @@ const path = require('path');
const _ = require('lodash');
const webpack = require('webpack');
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
const TARGET_PORT = _.get(process.env, 'npm_package_config_django_port', 8043);
const TARGET_HOST = _.get(process.env, 'npm_package_config_django_host', 'https://localhost');
@ -29,6 +29,7 @@ const watch = {
]
},
plugins: [
new HtmlWebpackHarddiskPlugin(),
new HardSourceWebpackPlugin({
cacheDirectory: 'node_modules/.cache/hard-source/[confighash]',
recordsPath: 'node_modules/.cache/hard-source/[confighash]/records.json',
@ -41,7 +42,6 @@ const watch = {
files: ['package.json']
}
}),
new HtmlWebpackHarddiskPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devServer: {

View File

@ -3,6 +3,8 @@ import AddController from './add-credentials.controller';
import EditController from './edit-credentials.controller';
import CredentialsStrings from './credentials.strings';
const MODULE_NAME = 'at.features.credentials';
const addEditTemplate = require('~features/credentials/add-edit-credentials.view.html');
function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) {
@ -51,12 +53,8 @@ CredentialsResolve.$inject = [
'OrganizationModel'
];
function CredentialsConfig ($stateExtenderProvider, legacyProvider, stringProvider) {
const stateExtender = $stateExtenderProvider.$get();
const legacy = legacyProvider.$get();
const strings = stringProvider.$get();
stateExtender.addState({
function CredentialsRun ($stateExtender, legacy, strings) {
$stateExtender.addState({
name: 'credentials.add',
route: '/add',
ncyBreadcrumb: {
@ -78,7 +76,7 @@ function CredentialsConfig ($stateExtenderProvider, legacyProvider, stringProvid
}
});
stateExtender.addState({
$stateExtender.addState({
name: 'credentials.edit',
route: '/:credential_id',
ncyBreadcrumb: {
@ -101,25 +99,27 @@ function CredentialsConfig ($stateExtenderProvider, legacyProvider, stringProvid
}
});
stateExtender.addState(legacy.getStateConfiguration('list'));
stateExtender.addState(legacy.getStateConfiguration('edit-permissions'));
stateExtender.addState(legacy.getStateConfiguration('add-permissions'));
stateExtender.addState(legacy.getStateConfiguration('add-organization'));
stateExtender.addState(legacy.getStateConfiguration('edit-organization'));
stateExtender.addState(legacy.getStateConfiguration('add-credential-type'));
stateExtender.addState(legacy.getStateConfiguration('edit-credential-type'));
$stateExtender.addState(legacy.getStateConfiguration('list'));
$stateExtender.addState(legacy.getStateConfiguration('edit-permissions'));
$stateExtender.addState(legacy.getStateConfiguration('add-permissions'));
$stateExtender.addState(legacy.getStateConfiguration('add-organization'));
$stateExtender.addState(legacy.getStateConfiguration('edit-organization'));
$stateExtender.addState(legacy.getStateConfiguration('add-credential-type'));
$stateExtender.addState(legacy.getStateConfiguration('edit-credential-type'));
}
CredentialsConfig.$inject = [
'$stateExtenderProvider',
'LegacyCredentialsServiceProvider',
'CredentialsStringsProvider'
CredentialsRun.$inject = [
'$stateExtender',
'LegacyCredentialsService',
'CredentialsStrings'
];
angular
.module('at.features.credentials', [])
.config(CredentialsConfig)
.module(MODULE_NAME, [])
.controller('AddController', AddController)
.controller('EditController', EditController)
.service('LegacyCredentialsService', LegacyCredentials)
.service('CredentialsStrings', CredentialsStrings);
.service('CredentialsStrings', CredentialsStrings)
.run(CredentialsRun);
export default MODULE_NAME;

View File

@ -1,5 +1,16 @@
import '~features/credentials';
import atLibServices from '~services';
import atLibComponents from '~components';
import atLibModels from '~models';
angular.module('at.features', [
'at.features.credentials'
import atFeaturesCredentials from '~features/credentials';
const MODULE_NAME = 'at.features';
angular.module(MODULE_NAME, [
atLibServices,
atLibComponents,
atLibModels,
atFeaturesCredentials
]);
export default MODULE_NAME;

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" ng-app="awApp">
<html>
<head>
<meta charset="utf-8">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" ng-app="awApp">
<html>
<head>
{% load staticfiles %}
<meta charset="utf-8">

View File

@ -1,3 +1,5 @@
import atLibServices from '~services';
import actionGroup from '~components/action/action-group.directive';
import divider from '~components/utility/divider.directive';
import form from '~components/form/form.directive';
@ -28,8 +30,12 @@ import truncate from '~components/truncate/truncate.directive';
import BaseInputController from '~components/input/base.controller';
import ComponentsStrings from '~components/components.strings';
const MODULE_NAME = 'at.lib.components';
angular
.module('at.lib.components', [])
.module(MODULE_NAME, [
atLibServices
])
.directive('atActionGroup', actionGroup)
.directive('atDivider', divider)
.directive('atForm', form)
@ -58,3 +64,5 @@ angular
.directive('atTruncate', truncate)
.service('BaseInputController', BaseInputController)
.service('ComponentsStrings', ComponentsStrings);
export default MODULE_NAME;

View File

@ -1,3 +1,5 @@
import atLibServices from '~services';
import Base from '~models/Base';
import Config from '~models/Config';
import Credential from '~models/Credential';
@ -5,11 +7,17 @@ import CredentialType from '~models/CredentialType';
import Me from '~models/Me';
import Organization from '~models/Organization';
const MODULE_NAME = 'at.lib.models';
angular
.module('at.lib.models', [])
.module(MODULE_NAME, [
atLibServices
])
.service('BaseModel', Base)
.service('ConfigModel', Config)
.service('CredentialModel', Credential)
.service('CredentialTypeModel', CredentialType)
.service('MeModel', Me)
.service('OrganizationModel', Organization);
export default MODULE_NAME;

View File

@ -1,11 +1,17 @@
import AppStrings from '~services/app.strings';
import BaseStringService from '~services/base-string.service';
import CacheService from '~services/cache.service';
import EventService from '~services/event.service';
import BaseStringService from '~services/base-string.service';
import AppStrings from '~services/app.strings';
const MODULE_NAME = 'at.lib.services';
angular
.module('at.lib.services', [])
.module(MODULE_NAME, [
'I18N'
])
.service('AppStrings', AppStrings)
.service('BaseStringService', BaseStringService)
.service('CacheService', CacheService)
.service('EventService', EventService);
export default MODULE_NAME;

View File

@ -1,7 +1,7 @@
// Configuration dependencies
global.$AnsibleConfig = null;
// Provided via Webpack DefinePlugin in webpack.config.js
global.$ENV = {} ;
global.$ENV = {};
// ui-router debugging
if ($ENV['route-debug']){
let trace = angular.module('ui.router').trace;
@ -14,7 +14,8 @@ if ($basePath) {
urlPrefix = `${$basePath}`;
}
// Modules
import start from './app.start';
import portalMode from './portal-mode/main';
import systemTracking from './system-tracking/main';
import inventoriesHosts from './inventories-hosts/main';
@ -46,71 +47,76 @@ import access from './access/main';
import scheduler from './scheduler/main';
import instanceGroups from './instance-groups/main';
import '../lib/components';
import '../lib/models';
import '../lib/services';
import '../features';
import atFeatures from '~features';
import atLibComponents from '~components';
import atLibModels from '~models';
import atLibServices from '~services';
angular.module('awApp', [
'I18N',
'AngularCodeMirrorModule',
'angular-duration-format',
'angularMoment',
'AngularScheduler',
'angular-md5',
'dndLists',
'ncy-angular-breadcrumb',
'ngSanitize',
'ngCookies',
'ngToast',
'gettext',
'Timezones',
'ui.router',
'ui.router.state.events',
'lrInfiniteScroll',
start.bootstrap(() => {
angular.bootstrap(document.body, ['awApp']);
});
about.name,
access.name,
license.name,
RestServices.name,
browserData.name,
configuration.name,
systemTracking.name,
inventoriesHosts.name,
inventoryScripts.name,
credentials.name,
credentialTypes.name,
organizations.name,
managementJobs.name,
breadCrumb.name,
home.name,
login.name,
activityStream.name,
workflowResults.name,
jobResults.name,
jobSubmission.name,
notifications.name,
standardOut.name,
Templates.name,
portalMode.name,
jobs.name,
teams.name,
users.name,
projects.name,
scheduler.name,
instanceGroups.name,
angular
.module('awApp', [
'I18N',
'AngularCodeMirrorModule',
'angular-duration-format',
'angularMoment',
'AngularScheduler',
'angular-md5',
'dndLists',
'ncy-angular-breadcrumb',
'ngSanitize',
'ngCookies',
'ngToast',
'gettext',
'Timezones',
'ui.router',
'ui.router.state.events',
'lrInfiniteScroll',
'Utilities',
'templates',
'PromptDialog',
'AWDirectives',
'features',
about.name,
access.name,
license.name,
RestServices.name,
browserData.name,
configuration.name,
systemTracking.name,
inventoriesHosts.name,
inventoryScripts.name,
credentials.name,
credentialTypes.name,
organizations.name,
managementJobs.name,
breadCrumb.name,
home.name,
login.name,
activityStream.name,
workflowResults.name,
jobResults.name,
jobSubmission.name,
notifications.name,
standardOut.name,
Templates.name,
portalMode.name,
jobs.name,
teams.name,
users.name,
projects.name,
scheduler.name,
instanceGroups.name,
'at.lib.components',
'at.lib.models',
'at.lib.services',
'at.features',
])
'Utilities',
'templates',
'PromptDialog',
'AWDirectives',
'features',
atFeatures,
atLibComponents,
atLibModels,
atLibServices
])
.constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/')
.constant('AngularScheduler.useTimezone', true)
.constant('AngularScheduler.showUTCField', true)
@ -171,13 +177,13 @@ angular.module('awApp', [
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService', 'AppStrings', 'I18NInit',
'FeaturesService', '$filter', 'SocketService', 'AppStrings',
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService, AppStrings, I18NInit) {
I18NInit();
$filter, SocketService, AppStrings) {
$rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) {
return $state.current.name.search(stateName) > 0;

View File

@ -0,0 +1,87 @@
const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'ja', 'nl'];
const DEFAULT_LOCALE = 'en';
const BASE_PATH = global.$basePath ? `${global.$basePath}languages/` : '/static/languages/';
/**
* The Angular app is manually initialized in order to complete some
* asynchronous work up front. This function returns a callback so app.js can
* call `angular.bootstrap` when the work is complete.
*
* @argument {function} - Callback.
*/
function bootstrap (callback) {
fetchLocaleStrings((locale) => {
if (locale) {
angular.module('I18N').constant('LOCALE', locale);
}
angular.element(document).ready(() => callback());
});
}
/**
* GET the localized JSON strings file or fall back to the default language
* if the locale isn't supported or if the request fails.
*
* @argument {function} - Callback.
*
* @returns {object=} - Locale data if it exists.
*/
function fetchLocaleStrings (callback) {
const code = getNormalizedLocaleCode();
if (isDefaultLocale(code) || !isSupportedLocale(code)) {
callback({ code });
return;
}
const request = $.ajax(`${BASE_PATH}${code}.json`);
request.done(res => {
if (res[code]) {
callback({ code, strings: res[code] });
} else {
callback({ code: DEFAULT_LOCALE });
}
});
request.fail(() => callback({ code: DEFAULT_LOCALE }));
}
/**
* Grabs the language off of navigator for browser compatibility.
* If the language isn't set, then it falls back to the DEFAULT_LOCALE. The
* locale code is normalized to be lowercase and 2 characters in length.
*/
function getNormalizedLocaleCode () {
let code;
if (navigator.languages && navigator.languages[0]) {
[code] = navigator.languages;
} else if (navigator.language) {
code = navigator.language;
} else {
code = navigator.userLanguage;
}
try {
code = code.split('-')[0].toLowerCase();
} catch (error) {
code = DEFAULT_LOCALE;
}
return code.substring(0, 2);
}
function isSupportedLocale (code) {
return SUPPORTED_LOCALES.includes(code);
}
function isDefaultLocale (code) {
return code === DEFAULT_LOCALE;
}
export default {
bootstrap
};

View File

@ -1,52 +1,36 @@
/* jshint ignore:start */
import { sprintf } from 'sprintf-js';
var sprintf = require('sprintf-js').sprintf;
let defaultLanguage = 'en_US';
function I18n (gettextCatalog) {
return {
N_,
sprintf,
_: s => gettextCatalog.getString(s),
translate: (singular, context) => gettextCatalog.getString(singular, context),
translatePlural: (count, singular, plural, context) => {
return gettextCatalog.getPlural(count, singular, plural, context);
},
hasTranslation: () => gettextCatalog.strings[gettextCatalog.currentLanguage] !== undefined
};
}
I18n.$inject = ['gettextCatalog'];
function run (LOCALE, gettextCatalog) {
if (LOCALE.code && LOCALE.strings) {
gettextCatalog.setCurrentLanguage(LOCALE.code);
gettextCatalog.setStrings(LOCALE.code, LOCALE.strings);
}
}
run.$inject = ['LOCALE', 'gettextCatalog'];
/**
* @ngdoc method
* @name function:i18n#N_
* @methodOf function:N_
* @description this function marks the translatable string with N_
* for 'grunt nggettext_extract'
*
*/
export function N_(s) {
return s;
}
export default
angular.module('I18N', [])
.factory('I18NInit', ['$window', 'gettextCatalog',
function ($window, gettextCatalog) {
return function() {
var langInfo = ($window.navigator.languages || [])[0] ||
$window.navigator.language ||
$window.navigator.userLanguage ||
'';
var langUrl = langInfo.replace('-', '_');
if (langUrl === defaultLanguage) {
return;
}
// gettextCatalog.debug = true;
gettextCatalog.setCurrentLanguage(langInfo);
gettextCatalog.loadRemote('/static/languages/' + langUrl + '.json');
};
}])
.factory('i18n', ['gettextCatalog',
function (gettextCatalog) {
return {
_: function (s) { return gettextCatalog.getString (s); },
N_: N_,
translate: (singular, context) => gettextCatalog.getString(singular, context),
translatePlural: (count, singular, plural, context) => {
return gettextCatalog.getPlural(count, singular, plural, context);
},
sprintf: sprintf,
hasTranslation: function () {
return gettextCatalog.strings[gettextCatalog.currentLanguage] !== undefined;
}
};
}]);
export default angular
.module('I18N', [
'gettext'
])
.factory('i18n', I18n)
.run(run);

View File

@ -16,16 +16,16 @@ module.exports = function(config) {
reporters: ['progress', 'coverage', 'junit'],
files:[
'./client/src/vendor.js',
'./client/src/app.js',
'./node_modules/angular-mocks/angular-mocks.js',
{ pattern: './tests/**/*-test.js' },
'client/src/**/*.html'
'./client/src/app.js',
'./tests/**/*-test.js',
'./client/src/**/*.html'
],
preprocessors: {
'./client/src/vendor.js': ['webpack', 'sourcemap'],
'./client/src/app.js': ['webpack', 'sourcemap'],
'./tests/**/*-test.js': ['webpack', 'sourcemap'],
'client/src/**/*.html': ['html2js']
'./client/src/vendor.js': ['webpack'],
'./client/src/app.js': ['webpack'],
'./tests/**/*-test.js': ['webpack'],
'./client/src/**/*.html': ['html2js']
},
webpack: webpackTestConfig,
webpackMiddleware: {