Skip to content

Repository files navigation

Typescript Localization and Properties file syntax parser

This is a typescript locale generator written in F# leveraging FParsec.

Localizing an application consists of having language files that will be used to generate strongly typed classes representing a language. The idea is to avoid hardcoding any language text in your actual application. This project wanted to take language properties files that were of the form

# This is an ignored comment, but will be parsed as part of the AST
Property = This is some localized text
OtherProperty = This is localized text with an argument {arg:number}
ThirdProperty = This is more localized text with another argument with no type {arg} = = This is a second line 

Stored in a directory structure like:

locales/
en-US/
global.properties
users.properties
storeFront.properties
fr-FR/
global.properties
users.properties
storeFront.properties

The goal is to take each properties file (which I called a "group") and to create strongly typed code for each locale ending up looking like

exportclassenUStestimplementscom.devshorts.data.locale.ITest{// localized functions Property():string{return"This is some localized text";}OtherProperty(arg:number):string{return"This is localized text with an argument "+arg.toString();}ThirdProperty(arg:any):string{return"This is more localized text with another argument with no type "+arg.toString()+"\r\n"+"\r\n"+" This is a second line";}// dictionary lookup publiclocaleDict:{[id:string] : (...args:any[])=>string;}={};constructor(){this.localeDict={"Property": $.proxy(this.Property,this),"OtherProperty": $.proxy(this.OtherProperty,this),"ThirdProperty": $.proxy(this.ThirdProperty,this)}}}

With a main language wrapper that looks like. Each group (such as 'users', 'test', or 'global') gets its own class and each language gets an "implementor" class.

module com.devshorts.data.locale{exportclassenUSimplementscom.devshorts.data.locale.ISr{test:com.devshorts.data.locale.enUStest=newcom.devshorts.data.locale.enUStest();// locale identifier localeType():string{return"en-us";}}exportclassenUStestimplementscom.devshorts.data.locale.ITest{// ... seen above }}

There is also a master interface that you can use to set and reference whatever is the current locale.

/*** CLASS IS AUTO GENERATED, CHANGES WILL NOT PERSIST*/
module com.devshorts.data.locale{exportinterfaceISr{test:ITest;localeType():string;}exportinterfaceITest{Property():string;OtherProperty(arg:number):string;ThirdProperty(arg:any):string;localeDict:{[id:string] : (...args:any[])=>string;};}}

So the final file system result might look like

locales/
en-US/
global.properties
users.properties
storeFront.properties
fr-FR/
global.properties
users.properties
storeFront.properties
com/
devshorts/
data/
locale/
ISr.ts
enUSLocale.ts
frFRLocale.ts

Usage

Usage of the locale generator is: <source locale folders> <generated output folder> <namespace> <master interface name>

So an example might be:

>TypeScriptLocaleWriter.exe locales com/devshorts/data/locale "com.devshorts.data.locale" ISr

In your code, you can maintain a localization singleton via a reference to the interface, such as

private sr:locale.ISr=newlocale.enUsLocale();

Or you can do dynamic lookups of localized fields via the dictionary lookup that each group has compiled. This lets you do things like buildling AngularJS localization filters or other dynamic locale lookups.

Language Overrides

You can also configure language overrides that will generate the appropriate typescript locale classes. A language override is where you want to have a superset of a specific language overriding only a certain set of properties.

For example, lets say you have an application geared focused to slightly different markets. 99% of the lexicon is the same, but for a few properties you may want to display something custom. Maybe you are localizing an app that uses the term "cable" but in some market the word "cable" means something different, and you want to just change all the references to "cable" to be "cord". The actual language is the same though, for example en-US. In this case you want to "overload" the property of

cable = cable

To be

cable = cord

The new language name will be en-US-cord. So, your properties may look like:

locale/
en-us/
global.properties
users.properties
en-us-cord/
global.properties

Notice you don't need to supply ALL the properties files, and they will NOT be auto generated (like normal languages), since these are only for overriding purposes. The contents of the global.properties in en-us-cord will only have properties that override en-us and no more. What will get generated however, is a typescript class called en-us-cord.ts that implements all of en-us except for what is overloaded by en-us-cord.

You can supply overridable suffixes to the normalizer. If you don't give it any, it will assume you have none.

Implementation

The locale generator is split into two sections. The first is a locale parser, which creates an AST using FParsec combinators. The first part will also normalize all locales. For example, if you add a property to the master language (right now its "en-US" but it is configurable), you want that property to be added to the .properties. files of all the other languages. Same if you remove a property, you want that property to be removed.

Using the first section you can get direct access to the properties AST, so you can implement other writers or do other syntax manipulation. Comment information is also stored in the AST so you can leverage that information as well if you need to.

The syntax tree that you will get back will be a LocaleElement list where a LocaleElement is a discriminated union that looks like

typeName= string
typeType= string
typePropertyName= string
typeArg=| WithType ofName*Type| NoType ofNametypeLocaleContents=| Argument ofArg| Text ofstring| Comment ofstring| NewLine
typeLocaleProperty=(PropertyName * LocaleContents list)typeLocaleElement=| Entry ofLocaleProperty| IgnoreEntry ofLocaleContents

The second part is the typescript writer, which takes the normalized locales and their groups, generates class and interface implementations, and merges all the files into the appropriate final form.

About

A typescript locale generator written in F#

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages