Path: blob/main/python/pylang/src/lib/gettext.py
1398 views
# vim:fileencoding=utf-81# License: BSD Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>23# noqa: eol-semicolon45# The Plural-Forms parser {{{6# From: https://github.com/SlexAxton/Jed/blob/master/jed.js licensed under the WTFPL78Jed = {}910vr'''11Jed.PF = {};1213Jed.PF.parse = function ( p ) {14var plural_str = Jed.PF.extractPluralExpr( p );15return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str);16};1718Jed.PF.compile = function ( p ) {19// Handle trues and falses as 0 and 120function imply( val ) {21return (val === true ? 1 : val ? val : 0);22}2324var ast = Jed.PF.parse( p );25return function ( n ) {26return imply( Jed.PF.interpreter( ast )( n ) );27};28};2930Jed.PF.interpreter = function ( ast ) {31return function ( n ) {32var res;33switch ( ast.type ) {34case 'GROUP':35return Jed.PF.interpreter( ast.expr )( n );36case 'TERNARY':37if ( Jed.PF.interpreter( ast.expr )( n ) ) {38return Jed.PF.interpreter( ast.truthy )( n );39}40return Jed.PF.interpreter( ast.falsey )( n );41case 'OR':42return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n );43case 'AND':44return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n );45case 'LT':46return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n );47case 'GT':48return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n );49case 'LTE':50return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n );51case 'GTE':52return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n );53case 'EQ':54return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n );55case 'NEQ':56return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n );57case 'MOD':58return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n );59case 'VAR':60return n;61case 'NUM':62return ast.val;63default:64throw new Error("Invalid Token found.");65}66};67};6869Jed.PF.extractPluralExpr = function ( p ) {70// trim first71p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, '');7273if (! /;\s*$/.test(p)) {74p = p.concat(';');75}7677var nplurals_re = /nplurals\=(\d+);/,78plural_re = /plural\=(.*);/,79nplurals_matches = p.match( nplurals_re ),80res = {},81plural_matches;8283// Find the nplurals number84if ( nplurals_matches.length > 1 ) {85res.nplurals = nplurals_matches[1];86}87else {88throw new Error('nplurals not found in plural_forms string: ' + p );89}9091// remove that data to get to the formula92p = p.replace( nplurals_re, "" );93plural_matches = p.match( plural_re );9495if (!( plural_matches && plural_matches.length > 1 ) ) {96throw new Error('`plural` expression not found: ' + p);97}98return plural_matches[ 1 ];99};100101/* Jison generated parser */102Jed.PF.parser = (function(){103104var parser = {trace: function trace() { },105yy: {},106symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1},107terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"},108productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]],109performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {110111var $0 = $$.length - 1;112switch (yystate) {113case 1: return { type : 'GROUP', expr: $$[$0-1] };114case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] };115break;116case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] };117break;118case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] };119break;120case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] };121break;122case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] };123break;124case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] };125break;126case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] };127break;128case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] };129break;130case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] };131break;132case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] };133break;134case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] };135break;136case 13:this.$ = { type: 'VAR' };137break;138case 14:this.$ = { type: 'NUM', val: Number(yytext) };139break;140}141},142table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}],143defaultActions: {6:[2,1]},144parseError: function parseError(str, hash) {145throw new Error(str);146},147parse: function parse(input) {148var self = this,149stack = [0],150vstack = [null], // semantic value stack151lstack = [], // location stack152table = this.table,153yytext = '',154yylineno = 0,155yyleng = 0,156recovering = 0,157TERROR = 2,158EOF = 1;159160//this.reductionCount = this.shiftCount = 0;161162this.lexer.setInput(input);163this.lexer.yy = this.yy;164this.yy.lexer = this.lexer;165if (typeof this.lexer.yylloc == 'undefined')166this.lexer.yylloc = {};167var yyloc = this.lexer.yylloc;168lstack.push(yyloc);169170if (typeof this.yy.parseError === 'function')171this.parseError = this.yy.parseError;172173function popStack (n) {174stack.length = stack.length - 2*n;175vstack.length = vstack.length - n;176lstack.length = lstack.length - n;177}178179function lex() {180var token;181token = self.lexer.lex() || 1; // $end = 1182// if token isn't its numeric value, convert183if (typeof token !== 'number') {184token = self.symbols_[token] || token;185}186return token;187}188189var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected, errStr;190while (true) {191// retreive state number from top of stack192state = stack[stack.length-1];193194// use default actions if available195if (this.defaultActions[state]) {196action = this.defaultActions[state];197} else {198if (symbol === null || symbol === undefined)199symbol = lex();200// read action for current state and first input201action = table[state] && table[state][symbol];202}203204// handle parse error205_handle_error:206if (typeof action === 'undefined' || !action.length || !action[0]) {207208if (!recovering) {209// Report error210expected = [];211for (p in table[state]) if (this.terminals_[p] && p > 2) {212expected.push("'"+this.terminals_[p]+"'");213}214errStr = '';215if (this.lexer.showPosition) {216errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";217} else {218errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +219(symbol == 1 /*EOF*/ ? "end of input" :220("'"+(this.terminals_[symbol] || symbol)+"'"));221}222this.parseError(errStr,223{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});224}225226// just recovered from another error227if (recovering == 3) {228if (symbol == EOF) {229throw new Error(errStr || 'Parsing halted.');230}231232// discard current lookahead and grab another233yyleng = this.lexer.yyleng;234yytext = this.lexer.yytext;235yylineno = this.lexer.yylineno;236yyloc = this.lexer.yylloc;237symbol = lex();238}239240// try to recover from error241while (1) {242// check for error recovery rule in this state243if ((TERROR.toString()) in table[state]) {244break;245}246if (state === 0) {247throw new Error(errStr || 'Parsing halted.');248}249popStack(1);250state = stack[stack.length-1];251}252253preErrorSymbol = symbol; // save the lookahead token254symbol = TERROR; // insert generic error symbol as new lookahead255state = stack[stack.length-1];256action = table[state] && table[state][TERROR];257recovering = 3; // allow 3 real symbols to be shifted before reporting a new error258}259260// this shouldn't happen, unless resolve defaults are off261if (action[0] instanceof Array && action.length > 1) {262throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);263}264265switch (action[0]) {266267case 1: // shift268//this.shiftCount++;269270stack.push(symbol);271vstack.push(this.lexer.yytext);272lstack.push(this.lexer.yylloc);273stack.push(action[1]); // push state274symbol = null;275if (!preErrorSymbol) { // normal execution/no error276yyleng = this.lexer.yyleng;277yytext = this.lexer.yytext;278yylineno = this.lexer.yylineno;279yyloc = this.lexer.yylloc;280if (recovering > 0)281recovering--;282} else { // error just occurred, resume old lookahead f/ before error283symbol = preErrorSymbol;284preErrorSymbol = null;285}286break;287288case 2: // reduce289//this.reductionCount++;290291len = this.productions_[action[1]][1];292293// perform semantic action294yyval.$ = vstack[vstack.length-len]; // default to $$ = $1295// default location, uses first token for firsts, last for lasts296yyval._$ = {297first_line: lstack[lstack.length-(len||1)].first_line,298last_line: lstack[lstack.length-1].last_line,299first_column: lstack[lstack.length-(len||1)].first_column,300last_column: lstack[lstack.length-1].last_column301};302r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);303304if (typeof r !== 'undefined') {305return r;306}307308// pop off stack309if (len) {310stack = stack.slice(0,-1*len*2);311vstack = vstack.slice(0, -1*len);312lstack = lstack.slice(0, -1*len);313}314315stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)316vstack.push(yyval.$);317lstack.push(yyval._$);318// goto new state = table[STATE][NONTERMINAL]319newState = table[stack[stack.length-2]][stack[stack.length-1]];320stack.push(newState);321break;322323case 3: // accept324return true;325}326327}328329return true;330}};/* Jison generated lexer */331var lexer = (function(){332333var lexer = ({EOF:1,334parseError:function parseError(str, hash) {335if (this.yy.parseError) {336this.yy.parseError(str, hash);337} else {338throw new Error(str);339}340},341setInput:function (input) {342this._input = input;343this._more = this._less = this.done = false;344this.yylineno = this.yyleng = 0;345this.yytext = this.matched = this.match = '';346this.conditionStack = ['INITIAL'];347this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};348return this;349},350input:function () {351var ch = this._input[0];352this.yytext+=ch;353this.yyleng++;354this.match+=ch;355this.matched+=ch;356var lines = ch.match(/\n/);357if (lines) this.yylineno++;358this._input = this._input.slice(1);359return ch;360},361unput:function (ch) {362this._input = ch + this._input;363return this;364},365more:function () {366this._more = true;367return this;368},369pastInput:function () {370var past = this.matched.substr(0, this.matched.length - this.match.length);371return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");372},373upcomingInput:function () {374var next = this.match;375if (next.length < 20) {376next += this._input.substr(0, 20-next.length);377}378return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");379},380showPosition:function () {381var pre = this.pastInput();382var c = new Array(pre.length + 1).join("-");383return pre + this.upcomingInput() + "\n" + c+"^";384},385next:function () {386if (this.done) {387return this.EOF;388}389if (!this._input) this.done = true;390391var token,392match,393col,394lines;395if (!this._more) {396this.yytext = '';397this.match = '';398}399var rules = this._currentRules();400for (var i=0;i < rules.length; i++) {401match = this._input.match(this.rules[rules[i]]);402if (match) {403lines = match[0].match(/\n.*/g);404if (lines) this.yylineno += lines.length;405this.yylloc = {first_line: this.yylloc.last_line,406last_line: this.yylineno+1,407first_column: this.yylloc.last_column,408last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length};409this.yytext += match[0];410this.match += match[0];411this.matches = match;412this.yyleng = this.yytext.length;413this._more = false;414this._input = this._input.slice(match[0].length);415this.matched += match[0];416token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);417if (token) return token;418else return;419}420}421if (this._input === "") {422return this.EOF;423} else {424this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),425{text: "", token: null, line: this.yylineno});426}427},428lex:function lex() {429var r = this.next();430if (typeof r !== 'undefined') {431return r;432} else {433return this.lex();434}435},436begin:function begin(condition) {437this.conditionStack.push(condition);438},439popState:function popState() {440return this.conditionStack.pop();441},442_currentRules:function _currentRules() {443return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;444},445topState:function () {446return this.conditionStack[this.conditionStack.length-2];447},448pushState:function begin(condition) {449this.begin(condition);450}});451lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {452453var YYSTATE=YY_START;454switch($avoiding_name_collisions) {455case 0:/* skip whitespace */456break;457case 1:return 20458break;459case 2:return 19460break;461case 3:return 8462break;463case 4:return 9464break;465case 5:return 6466break;467case 6:return 7468break;469case 7:return 11470break;471case 8:return 13472break;473case 9:return 10474break;475case 10:return 12476break;477case 11:return 14478break;479case 12:return 15480break;481case 13:return 16482break;483case 14:return 17484break;485case 15:return 18486break;487case 16:return 5488break;489case 17:return 'INVALID'490break;491}492};493lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./];494lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})()495parser.lexer = lexer;496return parser;497})();498'''499plural_forms_parser = Jed.PF500# }}}501502def _get_plural_forms_function(plural_forms_string):503return plural_forms_parser.compile(plural_forms_string or "nplurals=2; plural=(n != 1);")504505_gettext = def(text): return text506507_ngettext = def(text, plural, n): return text if n is 1 else plural508509def gettext(text):510return _gettext(text)511512def ngettext(text, plural, n):513return _ngettext(text, plural, n)514515def install(translation_data):516t = new Translations(translation_data)517t.install()518for func in register_callback.install_callbacks:519try:520func(t)521except:522pass523return t524525has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty)526527def register_callback(func):528# Register callbacks that will be called when new translation data is529# installed530register_callback.install_callbacks.push(func)531register_callback.install_callbacks = v'[]'532533empty_translation_data = {'entries': {}}534535class Translations:536537def __init__(self, translation_data):538translation_data = translation_data or empty_translation_data539func = _get_plural_forms_function(translation_data.plural_forms)540self.translations = [[translation_data, func]]541self.language = translation_data['language']542543def add_fallback(self, fallback):544fallback = fallback or empty_translation_data545func = _get_plural_forms_function(fallback.plural_forms)546self.translations.push([fallback, func])547548def gettext(self, text):549for t in self.translations:550m = t[0].entries551if has_prop(m, text):552return m[text][0]553return text554555def ngettext(self, text, plural, n):556for t in self.translations:557m = t[0].entries558if has_prop(m, text):559idx = t[1](n)560return m[text][idx] or (text if n is 1 else plural)561return text if n is 1 else plural562563def install(self):564nonlocal _gettext, _ngettext565_gettext = def ():566return self.gettext.apply(self, arguments)567_ngettext = def ():568return self.ngettext.apply(self, arguments)569570571