javascriptangularjsangular-ui-routertranslationangular-translate

AngularJS translate with UI Router


I have an application built with Angularjs using Angularjs Translate library - https://angular-translate.github.io/, and I have set it up with UI router because the current website has already been indexed by google with language versions like www.domain.com/en/us/ and I need to keep the same URL structure. The website has over 30 languages but it is a SPA (Single Page Application).

Below is my Javascript for the app configuration:

app.run(['$rootScope', '$translate', '$state', function ($rootScope, $translate, $state) {
    $rootScope.onChangeValue = function (e) {
        $rootScope.$broadcast("changeValue", e);
    };

    $rootScope.$on('$stateChangeSuccess', onStateChangeSuccess);

    function onStateChangeSuccess(event, toState, toParams) {
        var current = $translate.use();
        if (!current || current !== toParams.lang)
            $translate.use(toParams.lang);
    }
}]);

app.config(['$translateProvider', function ($translateProvider) {
    $translateProvider.useStaticFilesLoader({
        prefix: '../languages/locale-',
        suffix: '.json'
    });
    $translateProvider.fallbackLanguage('en');
    $translateProvider.preferredLanguage('en');
    $translateProvider.useSanitizeValueStrategy('escape');
    $translateProvider.useCookieStorage();
}]);

app.config(['$stateProvider', '$locationProvider', function ($stateProvider, $locationProvider) {
    $locationProvider.html5Mode(true);
    $locationProvider.hashPrefix('');

    $stateProvider.state('catalog', {
        url: '/{lang}',
        controller: "SudokuController",
        params: {
            lang: {
                value: function ($translate) {
                    return $translate.use();
                }
            }
        }
    });
}]);

and in the controller I have these functions:

    app.controller("SudokuController", ['$scope', 'Chronicle', '$timeout', '$cookies', '$translate', '$state', '$stateParams', function ($scope, Chronicle, $timeout, $cookies, $translate, $state, $stateParams) {

    $scope.params = $stateParams;
    $scope.changeLanguage = function (newLang) {
        var params = angular.extend($stateParams, { lang: newLang });

        $translate.use(newLang).then(function () {
            $state.transitionTo($state.current, params, {
                reload: true, inherit: false, notify: true
            });
        });
    }
}]);

Here is the HTML that is generating the nav with multiple languages:

<div class="language-dropdown">
    <ul class="country-list" ng-controller="navigationLanguages">
        <li class="country" ng-repeat="language in languagesMenu">
            <div class="country-flag" style="background-image: url('images/mk.png');"></div>
            <a ng-bind="language.Language" ng-click="changeLanguage(language.Code)"></a>
        </li>
    </ul>
</div>

So far I have achieved to add to the URL the chosen language, for instance, if I click on English I get www.domain.com/en, if I click on Spanish I get www.domain.com/es. But when I go directly to the www.domain.com/en I get Error 404 - Object not found!


Solution

    1. What does your server code look like? I'm guessing it's only serving up the index page on "/" so if you refresh your page on any route that isn't the root it won't serve anything up and you'll get not found. Basically for single page apps the routing should be handled completely client side because that is the piece that has the context about your app to know what to do. So always have your server serve up the index page no matter which route, that will let ui router take over and do its magic
    2. To add /en/us update your state config. Also, you don't really need to define params unless you need a default value
    $stateProvider.state('catalog', {
        url: '/{lang}/{region}',
        controller: "SudokuController"
    });
    
    1. Google's crawlers might be able to crawl a single page app like yours but most crawlers can't - so if you care about SEO you'll need to do some extra stuff. You have a couple options
      1. Pay for a service like https://prerender.io/ You have to set up a little bit of code on your server but for the most part they take care of making your site crawlable. I've used them in the past and recommend them. How does it work? Basically when a crawler visits your site instead of serving them up the single page app, prerender will serve them a static version of your site that has been pre-rendered that the crawler can understand.
      2. The second option is quite a bit of work. I've also done this (but on the newer version of angular) and don't recommend it, lots of headaches. Its called server side rendering. Basically instead of having another service create a prerendered version of your page, your server does it on the fly. The newer version of angular has some built in functionality for this, not sure about angularjs. I recommend googling more about prerendering and server side rendering there's more than I can say to do it justice here.