2016-01-27 01:07:11 +01:00
/ *
This file is part of TALER
( C ) 2016 GNUnet e . V .
TALER is free software ; you can redistribute it and / or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation ; either version 3 , or ( at your option ) any later version .
TALER is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License along with
2016-07-07 17:59:29 +02:00
TALER ; see the file COPYING . If not , see < http : / / www.gnu.org / licenses / >
2016-01-27 01:07:11 +01:00
* /
/ * *
* Generate . po file from list of source files .
2016-01-27 01:57:34 +01:00
*
* Note that duplicate message IDs are NOT merged , to get the same output as
* you would from xgettext , just run msguniq .
2016-03-01 19:46:20 +01:00
*
* @author Florian Dold
2016-01-27 01:07:11 +01:00
* /
"use strict" ;
import { readFileSync } from "fs" ;
import * as ts from "typescript" ;
function wordwrap ( str : string , width : number = 80 ) : string [ ] {
var regex = '.{1,' + width + '}(\\s|$)|\\S+(\\s|$)' ;
return str . match ( RegExp ( regex , 'g' ) ) ;
}
export function processFile ( sourceFile : ts.SourceFile ) {
processNode ( sourceFile ) ;
let lastTokLine = 0 ;
let preLastTokLine = 0 ;
function getTemplate ( node : ts.Node ) : string {
switch ( node . kind ) {
case ts . SyntaxKind . FirstTemplateToken :
return ( < any > node ) . text ;
case ts . SyntaxKind . TemplateExpression :
let te = < ts.TemplateExpression > node ;
let textFragments = [ te . head . text ] ;
for ( let tsp of te . templateSpans ) {
2016-02-23 15:58:05 +01:00
textFragments . push ( ` % ${ ( textFragments . length - 1 ) / 2 + 1 } $ s ` ) ;
2016-11-16 04:02:02 +01:00
textFragments . push ( tsp . literal . text . replace ( /%/g , "%%" ) ) ;
2016-01-27 01:07:11 +01:00
}
return textFragments . join ( '' ) ;
default :
return "(pogen.ts: unable to parse)" ;
}
}
2016-09-26 22:46:38 +02:00
function getComment ( node : ts.Node ) : string {
let lc = ts . getLineAndCharacterOfPosition ( sourceFile , node . pos ) ;
2016-01-27 01:07:11 +01:00
let lastComments ;
for ( let l = preLastTokLine ; l < lastTokLine ; l ++ ) {
let pos = ts . getPositionOfLineAndCharacter ( sourceFile , l , 0 ) ;
let comments = ts . getTrailingCommentRanges ( sourceFile . text , pos ) ;
if ( comments ) {
lastComments = comments ;
}
}
if ( ! lastComments ) {
return ;
}
let candidate = lastComments [ lastComments . length - 1 ] ;
let candidateEndLine = ts . getLineAndCharacterOfPosition ( sourceFile , candidate . end ) . line ;
2016-01-27 01:57:34 +01:00
if ( candidateEndLine != lc . line - 1 ) {
2016-01-27 01:07:11 +01:00
return ;
}
let text = sourceFile . text . slice ( candidate . pos , candidate . end ) ;
switch ( candidate . kind ) {
case ts . SyntaxKind . SingleLineCommentTrivia :
2016-01-27 01:19:20 +01:00
// Remove comment leader
2016-01-27 01:07:11 +01:00
text = text . replace ( /^[/][/]\s*/ , "" ) ;
break ;
case ts . SyntaxKind . MultiLineCommentTrivia :
2016-01-27 01:19:20 +01:00
// Remove comment leader and trailer,
// handling white space just like xgettext.
2016-01-27 01:07:11 +01:00
text = text
. replace ( /^[/][*](\s*?\n|\s*)?/ , "" )
. replace ( /(\n[ \t]*?)?[*][/]$/ , "" ) ;
break ;
}
return text ;
}
2016-09-26 22:46:38 +02:00
function getPath ( node : ts.Node ) : string [ ] {
switch ( node . kind ) {
case ts . SyntaxKind . PropertyAccessExpression :
let pae = < ts.PropertyAccessExpression > node ;
return Array . prototype . concat ( getPath ( pae . expression ) , [ pae . name . text ] ) ;
case ts . SyntaxKind . Identifier :
let id = < ts.Identifier > node ;
return [ id . text ] ;
}
return [ "(other)" ] ;
}
function arrayEq < T > ( a1 : T [ ] , a2 : T [ ] ) {
if ( a1 . length != a2 . length ) {
return false ;
}
for ( let i = 0 ; i < a1 . length ; i ++ ) {
if ( a1 [ i ] != a2 [ i ] ) {
return false ;
}
}
return true ;
}
interface TemplateResult {
comment : string ;
path : string [ ] ;
template : string ;
line : number ;
}
function processTaggedTemplateExpression ( tte : ts.TaggedTemplateExpression ) : TemplateResult {
let lc = ts . getLineAndCharacterOfPosition ( sourceFile , tte . pos ) ;
if ( lc . line != lastTokLine ) {
preLastTokLine = lastTokLine ;
lastTokLine = lc . line ;
}
let path = getPath ( tte . tag )
let res : TemplateResult = {
path ,
line : lc.line ,
comment : getComment ( tte ) ,
template : getTemplate ( tte . template ) . replace ( /"/g , '\\"' ) ,
} ;
return res ;
}
function formatMsgComment ( line : number , comment? : string ) {
if ( comment ) {
for ( let cl of comment . split ( '\n' ) ) {
console . log ( ` #. ${ cl } ` ) ;
}
}
console . log ( ` #: ${ sourceFile . fileName } : ${ line + 1 } ` ) ;
console . log ( ` #, c-format ` ) ;
}
function formatMsgLine ( head : string , msg : string ) {
// Do escaping, wrap break at newlines
let parts = msg
. match ( /(.*\n|.+$)/g )
. map ( ( x ) = > x . replace ( /\n/g , '\\n' ) )
. map ( ( p ) = > wordwrap ( p ) )
. reduce ( ( a , b ) = > a . concat ( b ) ) ;
if ( parts . length == 1 ) {
console . log ( ` ${ head } " ${ parts [ 0 ] } " ` ) ;
} else {
console . log ( ` ${ head } "" ` ) ;
for ( let p of parts ) {
console . log ( ` " ${ p } " ` ) ;
}
}
}
2016-11-16 04:02:02 +01:00
interface JsxProcessingContext {
}
function getJsxElementPath ( node : ts.Node ) {
let path ;
let process = ( childNode ) = > {
switch ( childNode . kind ) {
case ts . SyntaxKind . JsxOpeningElement :
{
let e = childNode as ts . JsxOpeningElement ;
return path = getPath ( e . tagName ) ;
}
default :
break ;
}
} ;
ts . forEachChild ( node , process ) ;
return path ;
}
function translateJsxExpression ( node : ts.Node , h ) {
switch ( node . kind ) {
case ts . SyntaxKind . StringLiteral :
{
let e = node as ts . StringLiteral ;
return e . text ;
}
default :
2016-11-19 00:52:50 +01:00
return ` % ${ h [ 0 ] ++ } $ s ` ;
2016-11-16 04:02:02 +01:00
}
}
function trim ( s ) {
return s . replace ( /^[ \n\t]*/ , "" ) . replace ( /[ \n\t]*$/ , "" ) ;
}
function getJsxContent ( node : ts.Node ) {
let fragments = [ ] ;
let holeNum = [ 1 ] ;
let process = ( childNode ) = > {
switch ( childNode . kind ) {
case ts . SyntaxKind . JsxText :
{
let e = childNode as ts . JsxText ;
2018-02-22 10:25:11 +01:00
let s = e . getFullText ( ) ;
let t = s . split ( "\n" ) . map ( trim ) . join ( " " ) ;
2016-11-16 04:02:02 +01:00
fragments . push ( t ) ;
}
case ts . SyntaxKind . JsxOpeningElement :
break ;
2016-11-23 01:00:28 +01:00
case ts . SyntaxKind . JsxElement :
fragments . push ( ` % ${ holeNum [ 0 ] ++ } $ s ` ) ;
break ;
2016-11-16 04:02:02 +01:00
case ts . SyntaxKind . JsxExpression :
{
let e = childNode as ts . JsxExpression ;
fragments . push ( translateJsxExpression ( e . expression , holeNum ) ) ;
break ;
}
case ts . SyntaxKind . JsxClosingElement :
break ;
default :
2016-11-23 00:55:54 +01:00
let lc = ts . getLineAndCharacterOfPosition ( childNode . getSourceFile ( ) , childNode . getStart ( ) ) ;
2016-11-23 00:59:01 +01:00
console . error ( ` unrecognized syntax in JSX Element ${ ts . SyntaxKind [ childNode . kind ] } ( ${ childNode . getSourceFile ( ) . fileName } : ${ lc . line + 1 } : ${ lc . character + 1 } ` ) ;
2016-11-16 04:02:02 +01:00
break ;
}
} ;
ts . forEachChild ( node , process ) ;
2018-02-22 10:25:11 +01:00
return fragments . join ( "" ) . trim ( ) . replace ( / +/g , " " ) ;
2016-11-16 04:02:02 +01:00
}
function getJsxSingular ( node : ts.Node ) {
let res ;
let process = ( childNode ) = > {
switch ( childNode . kind ) {
case ts . SyntaxKind . JsxElement :
{
let path = getJsxElementPath ( childNode ) ;
if ( arrayEq ( path , [ "i18n" , "TranslateSingular" ] ) ) {
res = getJsxContent ( childNode ) ;
}
}
default :
break ;
}
} ;
ts . forEachChild ( node , process ) ;
return res ;
}
function getJsxPlural ( node : ts.Node ) {
let res ;
let process = ( childNode ) = > {
switch ( childNode . kind ) {
case ts . SyntaxKind . JsxElement :
{
let path = getJsxElementPath ( childNode ) ;
if ( arrayEq ( path , [ "i18n" , "TranslatePlural" ] ) ) {
res = getJsxContent ( childNode ) ;
}
}
default :
break ;
}
} ;
ts . forEachChild ( node , process ) ;
return res ;
}
2016-09-26 22:46:38 +02:00
2016-01-27 01:07:11 +01:00
function processNode ( node : ts.Node ) {
switch ( node . kind ) {
2016-11-16 04:02:02 +01:00
case ts . SyntaxKind . JsxElement :
let path = getJsxElementPath ( node ) ;
if ( arrayEq ( path , [ "i18n" , "Translate" ] ) ) {
let content = getJsxContent ( node ) ;
let { line } = ts . getLineAndCharacterOfPosition ( sourceFile , node . pos ) ;
let comment = getComment ( node ) ;
formatMsgComment ( line , comment ) ;
formatMsgLine ( "msgid" , content ) ;
2016-11-19 00:52:50 +01:00
console . log ( ` msgstr "" ` ) ;
2016-11-16 04:02:02 +01:00
console . log ( ) ;
return ;
}
if ( arrayEq ( path , [ "i18n" , "TranslateSwitch" ] ) ) {
let { line } = ts . getLineAndCharacterOfPosition ( sourceFile , node . pos ) ;
let comment = getComment ( node ) ;
formatMsgComment ( line , comment ) ;
let singularForm = getJsxSingular ( node ) ;
if ( ! singularForm ) {
console . error ( "singular form missing" ) ;
process . exit ( 1 ) ;
}
let pluralForm = getJsxPlural ( node ) ;
if ( ! pluralForm ) {
console . error ( "plural form missing" ) ;
process . exit ( 1 ) ;
}
formatMsgLine ( "msgid" , singularForm ) ;
formatMsgLine ( "msgid_plural" , pluralForm ) ;
console . log ( ` msgstr[0] "" ` ) ;
console . log ( ` msgstr[1] "" ` ) ;
console . log ( ) ;
return ;
}
break ;
2016-09-26 22:46:38 +02:00
case ts . SyntaxKind . CallExpression :
{
// might be i18n.plural(i18n[.X]`...`, i18n[.X]`...`)
let ce = < ts.CallExpression > node ;
let path = getPath ( ce . expression ) ;
if ( ! arrayEq ( path , [ "i18n" , "plural" ] ) ) {
break ;
2016-01-27 01:07:11 +01:00
}
2016-09-26 22:46:38 +02:00
if ( ce . arguments [ 0 ] . kind != ts . SyntaxKind . TaggedTemplateExpression ) {
break ;
2016-01-27 01:07:11 +01:00
}
2016-09-26 22:46:38 +02:00
if ( ce . arguments [ 1 ] . kind != ts . SyntaxKind . TaggedTemplateExpression ) {
break ;
2016-01-27 01:07:11 +01:00
}
2016-09-26 22:46:38 +02:00
let { line } = ts . getLineAndCharacterOfPosition ( sourceFile , ce . pos ) ;
let t1 = processTaggedTemplateExpression ( < ts.TaggedTemplateExpression > ce . arguments [ 0 ] ) ;
let t2 = processTaggedTemplateExpression ( < ts.TaggedTemplateExpression > ce . arguments [ 1 ] ) ;
2016-09-26 22:53:41 +02:00
let comment = getComment ( ce ) ;
2016-09-26 22:46:38 +02:00
formatMsgComment ( line , comment ) ;
formatMsgLine ( "msgid" , t1 . template ) ;
formatMsgLine ( "msgid_plural" , t2 . template ) ;
console . log ( ` msgstr[0] "" ` ) ;
console . log ( ` msgstr[1] "" ` ) ;
console . log ( ) ;
// Important: no processing for child i18n expressions here
return ;
}
case ts . SyntaxKind . TaggedTemplateExpression :
{
let tte = < ts.TaggedTemplateExpression > node ;
let { comment , template , line , path } = processTaggedTemplateExpression ( tte ) ;
if ( path [ 0 ] != "i18n" ) {
break ;
}
formatMsgComment ( line , comment ) ;
formatMsgLine ( "msgid" , template ) ;
2016-01-27 01:07:11 +01:00
console . log ( ` msgstr "" ` ) ;
console . log ( ) ;
break ;
2016-09-26 22:46:38 +02:00
}
2016-01-27 01:07:11 +01:00
}
ts . forEachChild ( node , processNode ) ;
}
}
const fileNames = process . argv . slice ( 2 ) ;
console . log (
` # SOME DESCRIPTIVE TITLE.
# Copyright ( C ) YEAR THE PACKAGE ' S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package .
# FIRST AUTHOR < EMAIL @ ADDRESS > , YEAR .
#
# , fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\\n"
"Report-Msgid-Bugs-To: \\n"
2016-11-23 00:13:35 +01:00
"POT-Creation-Date: 2016-11-23 00:00+0100\\n"
2016-01-27 01:07:11 +01:00
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: LANGUAGE <LL@li.org>\\n"
"Language: \\n"
"MIME-Version: 1.0\\n"
2016-02-22 19:08:16 +01:00
"Content-Type: text/plain; charset=UTF-8\\n"
2016-01-27 01:07:11 +01:00
"Content-Transfer-Encoding: 8bit\\n" ` );
console . log ( )
2016-11-23 00:13:35 +01:00
fileNames . sort ( ) ;
2016-01-27 01:07:11 +01:00
fileNames . forEach ( fileName = > {
2016-11-16 04:02:02 +01:00
let sourceFile = ts . createSourceFile ( fileName , readFileSync ( fileName ) . toString ( ) , ts . ScriptTarget . ES2016 , /*setParentNodes */ true ) ;
2016-01-27 01:07:11 +01:00
processFile ( sourceFile ) ;
} ) ;