Skip to content

i18n: Able to use translation strings outside a template #11405

Closed

Description

@marcalj

I'm submitting a ... (check one with "x")

[x] feature request

Current behavior
#9104 (comment)

I don't think it is possible, like I said before it only works with static text, it won't parse text on the js code, only templates

Expected/desired behavior
Be able to translate strings used anywhere in the code, using an API.

Reproduction of the problem

What is the expected behavior?
I'm referencing the usage of $translate.instant to exposure real use cases:

  • Custom rendered text:
if (data._current_results === data._total) {
                content = this.$translate.instant('CLIPPINGS__LIST__SUMMARY_ALL', {'num': data._current_results});
            } else {
                if (undefined === data._total) {
                    data._total = '...';
                }

                content = this.$translate.instant('CLIPPINGS__LIST__SUMMARY', {
                    'num': data._current_results,
                    'total': data._total
                });
            }

            // Put 'mentions' first
            data = angular.merge({}, {
                mentions: mentions
            }, data);

            _.each(data, (value:number, key:string):void => {
                if (value) {
                    details += value + ' ' + this.$translate.instant('CLIPPINGS__LIST__SUMMARY_TYPE_' + key) + ', ';
                }
            });

            if (details) {
                details = '(' + _.trim(details, ', ') + ')';
            }

            content = content.replace(':details', details);

More examples:

Details - Getting image's file name from exported image of a HTML rendered report:
getExportImageName(hideExtension:boolean):string {
        let fileName:string;

        fileName = this.$translate.instant('D_CHART_FACET_authors__EXPORT_FILENAME', {
            'profileName': this.ExportService.GetProfileName(),
            'period': this.ExportService.GetPeriodString(this.SearchFilter.GetPeriodFromInterval())
        });

        if (!Boolean(hideExtension)) {
            fileName += '.png';
        }

        return fileName;
    }
  • Sometimes you're translating, sometimes use model data (could be very verbose in a template):
private _getTitle():string {
        if (this.inShareColumn) {
            return this.$translate.instant('COMPARISONS__SHARE_COLUMN_share_of_voice_TITLE');
        } else if (this.inTotalsColumn) {
            return this.$translate.instant('COMPARISONS__TOTAL_COLUMN_share_of_voice_TITLE');
        } else {
            return _.get<string>(this.group, 'profileName', '');
        }
    }
  • Using a third party chart plugin (Highcharts)
this.chart = new Highcharts.Chart(<any>{
            title: {
                text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone').toUpperCase(),
            },
            xAxis: {
                title: {
                    text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone_xaxis')
                }
            },
            yAxis: {
                min: 0,
                title: {
                    text: this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_TITLE_tone_yaxis')
                }
            },
            plotOptions: {
                scatter: {
                    tooltip: {
                        headerFormat: '<b>{point.key}</b><br>',
                        pointFormat: '{point.y} ' + this.$translate.instant('REPORTS_BLOG_MAPPING_CHART_mentions')
                    }
                }
            }
        });
  • To setup config variables and render the text without pipes as it's not always a translated string
this.config = {
            requiredList: true,
            bannedList: false,
            allowSpaces: false,
            allowComma: false,
            colorsType: false,
            defaultEnterAction: 'required',
            requiredTooltip: this.$translate.instant('D_CLIPPING_TAGS__REQUIRED_TOOLTIP'),
            bannedTooltip: this.$translate.instant('D_CLIPPING_TAGS__BANNED_TOOLTIP')
        };
  • To set window.title :) :
SetWindowTitle(title:string) {
        if (!!title) {
            this.$window.document.title = this.$translate.instant(title);
        }
    }
  • Custom date formatting:
dateHuman(date:Date):string {
        return date.getDate() + ' ' + this.$translate.instant('GLOBAL_CALENDAR_MONTH_' + date.getMonth())
            + ' ' + date.getFullYear();
    }
  • Sort things based on translated values:
// Sort types
            tmpTypes = _.sortBy(tmpTypes, (type:string):string => {
                // 'MISC' at the end
                if ('MISC' === type) {
                    return 'zzzzz';
                }

                return this.$translate.instant('FACET_phrases2__TYPE_' + type);
            });
GetSortedLanguages():IFacetLangDetectedCommonServiceLanguageObject[] {
        // We have to sort by translated languages!
        return _.sortBy(_.map(this.facetOptions, (item:string):any => {
            return {
                key: item,
                label: this.$translate.instant('FACET_langDetected_' + item),
                cssStyle: (_.includes(['english', 'catalan', 'spanish', 'french', 'italian'], item))
                    ? {'font-weight': 'bold'} : null,
                flag: _.get(this.lutFlags, item, null)
            };
        }), (item):string => {
            return item.label.toLowerCase();
        });
    }
  • Export raw data to CSV or Excel with translated values:
getDataExportStacked(inputData:any):any {
        let exportData = angular.copy(inputData);

        if (angular.isArray(exportData) && exportData.length) {
            exportData[0].name = this.$translate.instant('CLIPPINGS__CHARTS_volume_TITLE');

            exportData[0].data = _.map(exportData[0].data, (inputDataItem:any):any => {
                return {
                    'label': inputDataItem.association.profileName,
                    'value': inputDataItem.value
                };
            });
        }

        return exportData;
    }
  • Set config strings to third party plugins:
UpdateCalendarStrings():void {
        Highcharts.setOptions({
            lang: {
                months: [
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_January'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_February'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_March'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_April'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_May'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_June'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_July'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_August'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_September'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_October'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_November'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_December')
                ],
                shortMonths: [
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jan'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Feb'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Mar'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Apr'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_May'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jun'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Jul'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Aug'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Sep'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Oct'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Nov'),
                    this.$translate.instant('GLOBAL_CALENDAR_MONTH_SHORT_Dec')
                ],
                weekdays: [
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Sunday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Monday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Tuesday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Wednesday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Thursday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Friday'),
                    this.$translate.instant('GLOBAL_CALENDAR_DAY_Saturday')
                ]
            }
        });
    }
**What is the motivation / use case for changing the behavior?** Be able to translate strings outside templates.

Please tell us about your environment:

  • Angular version: 2.0.0-rc.6
  • Browser: [all]
  • Language: [TypeScript 2.0.2 | ES5 | SystemJS]

@vicb

Activity

  1. added
    featureLabel used to distinguish feature request from other issues
    area: i18nIssues related to localization and internationalization
    on Sep 7, 2016
  2. Mattes83 commented on Sep 20, 2016

    @Mattes83

    I think this is a real showstopper, i18n is not ready to use until this is implemented.
    E.g. I am not able to set translated validation messages in code

  3. marcalj commented on Sep 20, 2016

    @marcalj
    Author

    @Mattes83 Did you try this way?

    <p [hidden]="!alertManagerRowForm.controls.sendTo.hasError('validateEmail')" class="help-error">
        {{ 'ALERTMANAGER__FORM__FIELD__sendTo__ERROR__validateEmail' | translate }}
    </p>
  4. manklu commented on Sep 20, 2016

    @manklu

    😕 The documentation tells you how nice it is to have error messages in code and translation support requires anything in the template. Looks like there is more need for communication.

  5. Mattes83 commented on Sep 21, 2016

    @Mattes83

    @marcalj I know this way...but I need to have localized strings in my ts files.
    @manklu I totally agree

  6. vicb commented on Sep 30, 2016

    @vicb
    Contributor

    If someone want to start a design spec, it would be helpful (ie on Google Docs).

    It would need to list all the cases. Quickly thinking about it, I see a need for

    _.('static text');
    _.('with {{ parameter }}', {parameter: "parameter" });
    _.plural(count, {'few': '...'});
    _.select(on, {'value': '...'});
    
  7. fbobbio commented on Oct 14, 2016

    @fbobbio

    Hi guys, great feature request 👍

    Is there any suggested workaround to translate *.ts texts?

  8. mstawick commented on Oct 17, 2016

    @mstawick

    @fbobbio What I've been doing is create hidden elements in the template, ex.
    <span class="translation" #trans-foo i18n>foo</span>.

    Bind them using:
    @ViewChild('trans-foo) transFoo : ElementRef;.

    Then retrieve translated value
    transFoo.nativeElement.textContent.

    Looks retarded, but works for my needs.

  9. 188 remaining items

  10. JanneHarju commented on Feb 27, 2019

    @JanneHarju

    @ocombe

    How to use i18n-polyfills with const in .ts files

    export const glossayModel= () => [
    {
    title: 'test data',
    active: true,
    data: [

            [
                {
                    title: 'test data',
                    data: "test data."
                },
                {
                    title: 'test data',
                    data: "test data"
                },
                {
                    title: 'test data',
                    data: "test data"
                },
                {
                    title: 'test data',
                    data: "test data."
                },
                {
                    title: 'Past Due',
                    data: "test data."
                }
    

    ]]}];

    You can use like this
    {
    data: 'test data',
    title: this.i18n({ value: 'Name', id: 'name' }),
    }
    inject I18n polyfill in component constructor like this
    private i18n: I18n
    When using translations in ts file you need to use ngx-extractor and xliffmerge to get those ts files to translated. https://www.npmjs.com/package/ngx-i18nsupport

  11. shobhit12345 commented on Feb 27, 2019

    @shobhit12345

    @JanneHarju
    its not working , not getting the data in xlf file.

  12. JanneHarju commented on Feb 27, 2019

    @JanneHarju

    Here is my translation script:
    "translate-i18n": "ng xi18n --output-path locale && ngx-extractor -i src/**/*.ts projects/**/*.ts -f xlf -o src/locale/messages.xlf && xliffmerge --profile xliffmerge.json fi en",

    and here is xliffmerge.json

    {
      "xliffmergeOptions": {
        "srcDir": "src/locale",
        "genDir": "src/locale",
        "i18nFile": "messages.xlf",
        "defaultLanguage": "en",
        "useSourceAsTarget": true
      }
    }
    
    
  13. shobhit12345 commented on Feb 27, 2019

    @shobhit12345

    @JanneHarju
    Yes i m using same configuration , and able to extract .ts messages in xlf file .

    But const i have in different .ts files

    export const glossayModel= () => [
    {
    }

    i am importing in component , when i m trying to use i18n with const , it is not extracting the values .

  14. JanneHarju commented on Mar 7, 2019

    @JanneHarju

    What if you use const values like provider.

    {
        provide: glossayModel,
        useFactory: () => glossayModel()
    }
    

    code might be broken but you maybe get point.

  15. shobhit12345 commented on Mar 19, 2019

    @shobhit12345

    how to translate breadcrumbs

    const routes: Routes = [
    {
    path: './enroll-new.component',
    component: EnrollNewComponent,
    data: {
    breadcrumb: 'Enrollment'
    },
    }
    ]

  16. ngehlert commented on Mar 25, 2019

    @ngehlert

    can you people stop using this thread as a support thread for i18n-polyfill or other issues? if you have a question regarding i18n-polyfill go raise an issue in that repo.
    @ocombe maybe you can lock this topic? pretty much everything important should be covered.
    whats the angular version you plan on releasing the i18n support mentioned in this topic?

  17. locked and limited conversation to collaborators on Mar 25, 2019
  18. ocombe commented on Mar 25, 2019

    @ocombe
    Contributor

    Yes, locked for now.
    We are working hard to have ivy available with v8 as an opt-in beta. Runtime i18n will be usable with google closure (what google uses internally) which will let us debug it (it's a very big change). For everyone else you will be able to test ivy with i18n but you won't be able to load translations. The app will run with the original language used to code the app.
    The runtime service that is needed to actually translate currently returns the text untranslated. Since we're all working on fixing bugs with the existing code, new features will be limited to after the release. We'll work on it once v8 is out, it should be my first task.
    I'll add an update and unlock this thread once we have some news.

  19. petebacondarwin commented on Aug 8, 2019

    @petebacondarwin
    Contributor

    For what it is worth the new $localize tag that we are working on can be used programatically.

    E.g.

    const name = 'World';
    $localize `Hello ${name}:name!`;
    

    And then will be able to provide translations for Hello {$name}! that can be replaced at compile-time (or run-time).

  20. removed their assignment
    on Dec 2, 2019
  21. petebacondarwin commented on Sep 25, 2020

    @petebacondarwin
    Contributor

    This is now available (if not documented).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: i18nIssues related to localization and internationalizationfeatureLabel used to distinguish feature request from other issuesfreq3: high

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions