1

I am new to Angular and have been starting to build a new project with the next characteristics. Sorry if the explanation is not clear enough. I'm more than glad to provide more information but don't want to through my complete project to have irrelevant information.

I'm using

  • Visual Studio 2015
  • ASPNET CORE
  • AspNetCore.Angular Services
  • ngx-translate

I've followed the examples and instructions provided in [https://github.com/ngx-translate/core][1]

My code is working as expected being able to translate 4 languages and use the pipe directive in multiple components inside a router. This means that my json files are being loaded and are structured properly. I saw many reports in which there was an error on the json files but it is not my case.

However my problem is that i can't add the lines that setup the default language and language to be used in the constructor.

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
    selector: 'app',
    template: require('./app.component.html'),
    styles: [require('./app.component.css')]
})
export class AppComponent {
    constructor(private translate: TranslateService) {

        
        translate.addLangs(["en", "fr", "es", "de"]);
        //Error shows up when uncommenting any of the next two lines
        //translate.setDefaultLang('en');
        //translate.use('en');

    }
}

Whenever i uncomment these lines i get the error

Exception: Call to Node module failed with error: SyntaxError: Unexpected token  in JSON at position 0 at JSON.parse ()

If I comment these lines my application launches without problem and the only detail is that the language has not been setup yet. I can manually select it in a dropdown list and then everything works as expected.

Here is the html file

<div id="MainContainer" class='container-fluid'>
    <div class='row'>
        <label>
          {{ "SELECT" | translate }}<br />
          <select #langSelect (change)="translate.use(langSelect.value)">
            <option *ngFor="let lang of translate.getLangs()" [value]="lang" [selected]="lang === translate.currentLang">{{ lang }}</option>
          </select>
        </label>
    </div>
</div>

Here is my app module

import { NgModule }                 from '@angular/core';
import { UniversalModule }          from 'angular2-universal'; // this automatically imports BrowserModule, HttpModule, and JsonpModule too.
import { Http }                     from '@angular/http';
import { FormsModule }              from '@angular/forms'; // <-- ngModel lives here
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader }      from '@ngx-translate/http-loader';

// main Routing
import { AppRoutingModule }         from './routers/app-routing.module';


// own Services
import { ImgService }               from './services/img.service';

// own components
import { AppComponent }             from './components/app/app.component';
import { NavMenuComponent }         from './components/nav-menu/nav-menu.component';
import { CompleteSystemComponent }  from './components/complete-system/complete-system.component';
import { VehicleComponent }         from './components/vehicle/vehicle.component';
import { RobotComponent }           from './components/robot/robot.component';
import { MapComponent }             from './components/map/map.component';
import { StatusBarComponent }       from './components/status-bar/status-bar.component';
import { RobotCameraComponent }     from './components/robot-camera/robot-camera.component';
import { ImageProcessingComponent } from './components/image-processing/image-processing.component';
import { Station1Component }        from './components/station1/station1.component';
import { Station2Component }        from './components/station2/station2.component';


//AoT requires an exported function for factories
export function HttpLoaderFactory(http: Http) {
    //return new TranslateHttpLoader(http, '/i18n/', '.json');
    //return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
    return new TranslateHttpLoader(http);
}


@NgModule({
    imports: [
        UniversalModule, // must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
        FormsModule,
        AppRoutingModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [Http]
            }
        }),

    ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        RobotComponent,
        VehicleComponent,
        CompleteSystemComponent,
        MapComponent,
        StatusBarComponent,
        RobotCameraComponent,
        ImageProcessingComponent,
        Station1Component,
        Station2Component
    ],
    providers: [
        ImgService
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

I would appreciate if some one could give me a hint on what to try. So far these are the things that I've tried to solve the problem

  • Move the definition of default language and use to ngOnInit (failed the same way)
  • Move the definition of default language and use to a function called with a button (It work without problems)
  • Try to move the definition of default language and use to ngAfterViewInit() and ngAfterViewChecked().

I suspect that the problem is that the json files have not been loaded from the wwwroot directory or that the service is not able to find them there when the application is launching. After the application launched it finds them without a problem and everything works as expected.

Thanks again for your comments and let me know if more Information would help debug this issue

3 Answers3

1

I finally managed to solve almost 80% of this problem (I'm new to angular as well and would love to see better solution).

  1. Update all packages to latest version (for me it's angular 4.3.4).

  2. Change to HttpClient as in the docs(https://github.com/ngx-translate/core)

Here is my app.module.shared.ts file:

import { NgModule,Inject } from '@angular/core';
import { RouterModule,Router } from '@angular/router';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpModule } from '@angular/http';

import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';

export function HttpLoaderFactory(http: HttpClient) {
    return new TranslateHttpLoader(http);
}

export const sharedConfig: NgModule = {
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        CounterComponent,
        FetchDataComponent,
        HomeComponent,
    ],
    imports: [
        HttpClientModule,
        TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: HttpLoaderFactory,
                deps: [HttpClient]
            }
        }),
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: 'counter', component: CounterComponent },
            { path: 'fetch-data', component: FetchDataComponent },
            { path: '**', redirectTo: 'home' }
        ])
    ]
};

And here is the interesting part. The only problem is translating of ui is done in the browser which is bad for SEO. I will update if I figure something out.

app.component.ts file:

import { Component, OnInit, Inject } from '@angular/core';

import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

import { TranslateService } from '@ngx-translate/core';

@Component({
    selector: 'app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    isBrowser: boolean;


    constructor( @Inject(PLATFORM_ID) platformId: Object ,@Inject('ORIGIN_URL') originUrl: string, private translate: TranslateService) {
        this.isBrowser = isPlatformBrowser(platformId);

        if (this.isBrowser) {
            translate.setDefaultLang('en');
            translate.use('en');
        }

    }
    changeLang(lang: string) {
        this.translate.use(lang);
    }
}

That solved it for now, and if you figure out a better solution that doesn't miss up SEO share it :).

Jonathan
  • 6,507
  • 5
  • 37
  • 47
Abdo
  • 322
  • 6
  • 15
0

I had the same problem, that's how I solved it:

1) Provide Vocabulary to View:

public async Task<IActionResult> Index()
{
    var serverData = new IndexViewModel
    {
        Vocabulary = await GetUserLanguage()
    };

    return View(serverData);
}

Vocabulary:

public class Vocabulary
{
    public String Language { get; set; }

    public Object Translations { get; set; }
}

GetUserLanguage():

private async Task<Vocabulary> GetUserLanguage()
    {
        // Get lang form request headers
        StringValues langs;
        HttpContext.Request.Headers.TryGetValue("Accept-Language", out langs);

        if(langs.Count == 0)
        {
            return null;
        }

        var langsArr = langs.ToArray();

        for(var i = 0; i < langsArr.Length; i++)
        {
            var lang = langsArr[i];

            if (lang.Contains("-"))
            {
                langsArr[i] = lang.Split("-")[0];
            }

            if (lang.Contains("_"))
            {
                langsArr[i] = lang.Split("_")[0];
            }
        }

        langsArr = langsArr == null
            ? new String[] { "en" }
            : langsArr;

        Vocabulary vocabulary = null;

        foreach (var userLang in langsArr)
        {
            var path = $"{_hostingEnvironment.WebRootPath}/assets/i18n/{userLang}.json";
            FileInfo fileInfo = new FileInfo(path);

            if (fileInfo.Exists)
            {
                vocabulary = new Vocabulary
                {
                    Language = userLang,
                    Translations = await System.IO.File.ReadAllTextAsync(path)
                };

                break;
            }
        }

        return vocabulary;
    }

2) Send the model to the boot.server.

Index.cshtml:

<app asp-prerender-module="ClientApp/dist/main-server" asp-prerender-data="@JavaScriptHelper.Json(Model)">Loading...</app>

JavaScriptHelper.Json():

public static string Json(object obj)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Converters = new JsonConverter[]
            {
                new StringEnumConverter(),
            },
            StringEscapeHandling = StringEscapeHandling.EscapeHtml
        };

        return JsonConvert.SerializeObject(obj, settings);
    }

Passing data from MVC to Angular2 described here

In the boot.server.ts you can access the passed server data via params.data: // Imports

declare var global: any;

enableProdMode();

export default createServerRenderer(params => {
    let serverData = JSON.parse(params.data);
    serverData.vocabulary.translations = 
JSON.parse(serverData.vocabulary.translations);

global.serverData = serverData;

// ...

return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
    const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
    const state = moduleRef.injector.get(PlatformState);
    const zone = moduleRef.injector.get(NgZone);

return new Promise<RenderResult>((resolve, reject) => {
        zone.onError.subscribe((errorInfo: any) => reject(errorInfo));
        appRef.isStable.first(isStable => isStable).subscribe(() => {
            // Because 'onStable' fires before 'onError', we have to delay slightly before
            // completing the request in case there's an error to report
            setImmediate(() => {
                resolve({
                    html: state.renderToString(),

                    globals: {
                        serverData: serverData
                    }
                });
                moduleRef.destroy();
            });
        });
    });

Object global is available during the prerendering in the node environment.

globals: { serverData: serverData } past data to the window object, which is available in the browser

3) Set default lang in the app.component.ts: declare var serverData: any;

@Component({
    selector: 'app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

constructor(private translate: TranslateService, private http: Http, globalService: GlobalService, @Inject(PLATFORM_ID) platformId: Object) {
    let isBrowser = isPlatformBrowser(platformId);

    let lang = '';
    let translations = { };

    if (!isBrowser) {
        lang = serverData.vocabulary.language;
        translations = serverData.vocabulary.translations;
    } else {
        lang = (window as any).serverData.vocabulary.language;
        translations = (window as any).serverData.vocabulary.translations;
    }

    translate.setTranslation(lang, translations);
    translate.setDefaultLang(lang);
    translate.use(lang);
    }
}
Nazir Temirov
  • 121
  • 1
  • 5
0

<div id="MainContainer" class='container-fluid'>
    <div class='row'>
        <label>
          {{ "SELECT" | translate }}<br />
          <select #langSelect (change)="translate.use(langSelect.value)">
            <option *ngFor="let lang of translate.getLangs()" [value]="lang" [selected]="lang === translate.currentLang">{{ lang }}</option>
          </select>
        </label>
    </div>
</div>