Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117751798
l10n.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
24 KB
Referenced Files
None
Subscribers
None
l10n.js
View Options
/** Copyright (c) 2011-2012 Fabien Cazenave, Mozilla.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/*
Additional modifications for PDF.js project:
- Disables language initialization on page loading;
- Adds fallback argument to the getL10nData;
- Removes consoleLog and simplifies consoleWarn;
- Removes window._ assignment.
*/
/*jshint browser: true, devel: true, es5: true, globalstrict: true */
'use strict'
;
document
.
webL10n
=
(
function
(
window
,
document
,
undefined
)
{
var
gL10nData
=
{};
var
gTextData
=
''
;
var
gTextProp
=
'textContent'
;
var
gLanguage
=
''
;
var
gMacros
=
{};
var
gReadyState
=
'loading'
;
// read-only setting -- we recommend to load l10n resources synchronously
var
gAsyncResourceLoading
=
true
;
// debug helpers
function
consoleWarn
(
message
)
{
console
.
log
(
'[l10n] '
+
message
);
};
/**
* DOM helpers for the so-called "HTML API".
*
* These functions are written for modern browsers. For old versions of IE,
* they're overridden in the 'startup' section at the end of this file.
*/
function
getL10nResourceLinks
()
{
return
document
.
querySelectorAll
(
'link[type="application/l10n"]'
);
}
function
getTranslatableChildren
(
element
)
{
return
element
?
element
.
querySelectorAll
(
'*[data-l10n-id]'
)
:
[];
}
function
getL10nAttributes
(
element
)
{
if
(
!
element
)
return
{};
var
l10nId
=
element
.
getAttribute
(
'data-l10n-id'
);
var
l10nArgs
=
element
.
getAttribute
(
'data-l10n-args'
);
var
args
=
{};
if
(
l10nArgs
)
{
try
{
args
=
JSON
.
parse
(
l10nArgs
);
}
catch
(
e
)
{
consoleWarn
(
'could not parse arguments for #'
+
l10nId
);
}
}
return
{
id
:
l10nId
,
args
:
args
};
}
function
fireL10nReadyEvent
(
lang
)
{
var
evtObject
=
document
.
createEvent
(
'Event'
);
evtObject
.
initEvent
(
'localized'
,
false
,
false
);
evtObject
.
language
=
lang
;
window
.
dispatchEvent
(
evtObject
);
}
/**
* l10n resource parser:
* - reads (async XHR) the l10n resource matching `lang';
* - imports linked resources (synchronously) when specified;
* - parses the text data (fills `gL10nData' and `gTextData');
* - triggers success/failure callbacks when done.
*
* @param {string} href
* URL of the l10n resource to parse.
*
* @param {string} lang
* locale (language) to parse.
*
* @param {Function} successCallback
* triggered when the l10n resource has been successully parsed.
*
* @param {Function} failureCallback
* triggered when the an error has occured.
*
* @return {void}
* uses the following global variables: gL10nData, gTextData, gTextProp.
*/
function
parseResource
(
href
,
lang
,
successCallback
,
failureCallback
)
{
var
baseURL
=
href
.
replace
(
/\/[^\/]*$/
,
'/'
);
// handle escaped characters (backslashes) in a string
function
evalString
(
text
)
{
if
(
text
.
lastIndexOf
(
'\\'
)
<
0
)
return
text
;
return
text
.
replace
(
/\\\\/g
,
'\\'
)
.
replace
(
/\\n/g
,
'\n'
)
.
replace
(
/\\r/g
,
'\r'
)
.
replace
(
/\\t/g
,
'\t'
)
.
replace
(
/\\b/g
,
'\b'
)
.
replace
(
/\\f/g
,
'\f'
)
.
replace
(
/\\{/g
,
'{'
)
.
replace
(
/\\}/g
,
'}'
)
.
replace
(
/\\"/g
,
'"'
)
.
replace
(
/\\'/g
,
"'"
);
}
// parse *.properties text data into an l10n dictionary
function
parseProperties
(
text
)
{
var
dictionary
=
[];
// token expressions
var
reBlank
=
/^\s*|\s*$/
;
var
reComment
=
/^\s*#|^\s*$/
;
var
reSection
=
/^\s*\[(.*)\]\s*$/
;
var
reImport
=
/^\s*@import\s+url\((.*)\)\s*$/i
;
var
reSplit
=
/^([^=\s]*)\s*=\s*(.+)$/
;
// TODO: escape EOLs with '\'
// parse the *.properties file into an associative array
function
parseRawLines
(
rawText
,
extendedSyntax
)
{
var
entries
=
rawText
.
replace
(
reBlank
,
''
).
split
(
/[\r\n]+/
);
var
currentLang
=
'*'
;
var
genericLang
=
lang
.
replace
(
/-[a-z]+$/i
,
''
);
var
skipLang
=
false
;
var
match
=
''
;
for
(
var
i
=
0
;
i
<
entries
.
length
;
i
++
)
{
var
line
=
entries
[
i
];
// comment or blank line?
if
(
reComment
.
test
(
line
))
continue
;
// the extended syntax supports [lang] sections and @import rules
if
(
extendedSyntax
)
{
if
(
reSection
.
test
(
line
))
{
// section start?
match
=
reSection
.
exec
(
line
);
currentLang
=
match
[
1
];
skipLang
=
(
currentLang
!==
'*'
)
&&
(
currentLang
!==
lang
)
&&
(
currentLang
!==
genericLang
);
continue
;
}
else
if
(
skipLang
)
{
continue
;
}
if
(
reImport
.
test
(
line
))
{
// @import rule?
match
=
reImport
.
exec
(
line
);
loadImport
(
baseURL
+
match
[
1
]);
// load the resource synchronously
}
}
// key-value pair
var
tmp
=
line
.
match
(
reSplit
);
if
(
tmp
&&
tmp
.
length
==
3
)
dictionary
[
tmp
[
1
]]
=
evalString
(
tmp
[
2
]);
}
}
// import another *.properties file
function
loadImport
(
url
)
{
loadResource
(
url
,
function
(
content
)
{
parseRawLines
(
content
,
false
);
// don't allow recursive imports
},
false
,
false
);
// load synchronously
}
// fill the dictionary
parseRawLines
(
text
,
true
);
return
dictionary
;
}
// load the specified resource file
function
loadResource
(
url
,
onSuccess
,
onFailure
,
asynchronous
)
{
var
xhr
=
new
XMLHttpRequest
();
xhr
.
open
(
'GET'
,
url
,
asynchronous
);
if
(
xhr
.
overrideMimeType
)
{
xhr
.
overrideMimeType
(
'text/plain; charset=utf-8'
);
}
xhr
.
onreadystatechange
=
function
()
{
if
(
xhr
.
readyState
==
4
)
{
if
(
xhr
.
status
==
200
||
xhr
.
status
===
0
)
{
if
(
onSuccess
)
onSuccess
(
xhr
.
responseText
);
}
else
{
if
(
onFailure
)
onFailure
();
}
}
};
xhr
.
send
(
null
);
}
// load and parse l10n data (warning: global variables are used here)
loadResource
(
href
,
function
(
response
)
{
gTextData
+=
response
;
// mostly for debug
// parse *.properties text data into an l10n dictionary
var
data
=
parseProperties
(
response
);
// find attribute descriptions, if any
for
(
var
key
in
data
)
{
var
id
,
prop
,
index
=
key
.
lastIndexOf
(
'.'
);
if
(
index
>
0
)
{
// an attribute has been specified
id
=
key
.
substring
(
0
,
index
);
prop
=
key
.
substr
(
index
+
1
);
}
else
{
// no attribute: assuming text content by default
id
=
key
;
prop
=
gTextProp
;
}
if
(
!
gL10nData
[
id
])
{
gL10nData
[
id
]
=
{};
}
gL10nData
[
id
][
prop
]
=
data
[
key
];
}
// trigger callback
if
(
successCallback
)
successCallback
();
},
failureCallback
,
gAsyncResourceLoading
);
};
// load and parse all resources for the specified locale
function
loadLocale
(
lang
,
callback
)
{
clear
();
gLanguage
=
lang
;
// check all <link type="application/l10n" href="..." /> nodes
// and load the resource files
var
langLinks
=
getL10nResourceLinks
();
var
langCount
=
langLinks
.
length
;
if
(
langCount
==
0
)
{
consoleWarn
(
'no resource to load, early way out'
);
fireL10nReadyEvent
(
lang
);
gReadyState
=
'complete'
;
return
;
}
// start the callback when all resources are loaded
var
onResourceLoaded
=
null
;
var
gResourceCount
=
0
;
onResourceLoaded
=
function
()
{
gResourceCount
++
;
if
(
gResourceCount
>=
langCount
)
{
if
(
callback
)
// execute the [optional] callback
callback
();
fireL10nReadyEvent
(
lang
);
gReadyState
=
'complete'
;
}
};
// load all resource files
function
l10nResourceLink
(
link
)
{
var
href
=
link
.
href
;
var
type
=
link
.
type
;
this
.
load
=
function
(
lang
,
callback
)
{
var
applied
=
lang
;
parseResource
(
href
,
lang
,
callback
,
function
()
{
consoleWarn
(
href
+
' not found.'
);
applied
=
''
;
});
return
applied
;
// return lang if found, an empty string if not found
};
}
for
(
var
i
=
0
;
i
<
langCount
;
i
++
)
{
var
resource
=
new
l10nResourceLink
(
langLinks
[
i
]);
var
rv
=
resource
.
load
(
lang
,
onResourceLoaded
);
if
(
rv
!=
lang
)
{
// lang not found, used default resource instead
consoleWarn
(
'"'
+
lang
+
'" resource not found'
);
gLanguage
=
''
;
}
}
}
// clear all l10n data
function
clear
()
{
gL10nData
=
{};
gTextData
=
''
;
gLanguage
=
''
;
// TODO: clear all non predefined macros.
// There's no such macro /yet/ but we're planning to have some...
}
/**
* Get rules for plural forms (shared with JetPack), see:
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
* https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
*
* @param {string} lang
* locale (language) used.
*
* @return {Function}
* returns a function that gives the plural form name for a given integer:
* var fun = getPluralRules('en');
* fun(1) -> 'one'
* fun(0) -> 'other'
* fun(1000) -> 'other'.
*/
function
getPluralRules
(
lang
)
{
var
locales2rules
=
{
'af'
:
3
,
'ak'
:
4
,
'am'
:
4
,
'ar'
:
1
,
'asa'
:
3
,
'az'
:
0
,
'be'
:
11
,
'bem'
:
3
,
'bez'
:
3
,
'bg'
:
3
,
'bh'
:
4
,
'bm'
:
0
,
'bn'
:
3
,
'bo'
:
0
,
'br'
:
20
,
'brx'
:
3
,
'bs'
:
11
,
'ca'
:
3
,
'cgg'
:
3
,
'chr'
:
3
,
'cs'
:
12
,
'cy'
:
17
,
'da'
:
3
,
'de'
:
3
,
'dv'
:
3
,
'dz'
:
0
,
'ee'
:
3
,
'el'
:
3
,
'en'
:
3
,
'eo'
:
3
,
'es'
:
3
,
'et'
:
3
,
'eu'
:
3
,
'fa'
:
0
,
'ff'
:
5
,
'fi'
:
3
,
'fil'
:
4
,
'fo'
:
3
,
'fr'
:
5
,
'fur'
:
3
,
'fy'
:
3
,
'ga'
:
8
,
'gd'
:
24
,
'gl'
:
3
,
'gsw'
:
3
,
'gu'
:
3
,
'guw'
:
4
,
'gv'
:
23
,
'ha'
:
3
,
'haw'
:
3
,
'he'
:
2
,
'hi'
:
4
,
'hr'
:
11
,
'hu'
:
0
,
'id'
:
0
,
'ig'
:
0
,
'ii'
:
0
,
'is'
:
3
,
'it'
:
3
,
'iu'
:
7
,
'ja'
:
0
,
'jmc'
:
3
,
'jv'
:
0
,
'ka'
:
0
,
'kab'
:
5
,
'kaj'
:
3
,
'kcg'
:
3
,
'kde'
:
0
,
'kea'
:
0
,
'kk'
:
3
,
'kl'
:
3
,
'km'
:
0
,
'kn'
:
0
,
'ko'
:
0
,
'ksb'
:
3
,
'ksh'
:
21
,
'ku'
:
3
,
'kw'
:
7
,
'lag'
:
18
,
'lb'
:
3
,
'lg'
:
3
,
'ln'
:
4
,
'lo'
:
0
,
'lt'
:
10
,
'lv'
:
6
,
'mas'
:
3
,
'mg'
:
4
,
'mk'
:
16
,
'ml'
:
3
,
'mn'
:
3
,
'mo'
:
9
,
'mr'
:
3
,
'ms'
:
0
,
'mt'
:
15
,
'my'
:
0
,
'nah'
:
3
,
'naq'
:
7
,
'nb'
:
3
,
'nd'
:
3
,
'ne'
:
3
,
'nl'
:
3
,
'nn'
:
3
,
'no'
:
3
,
'nr'
:
3
,
'nso'
:
4
,
'ny'
:
3
,
'nyn'
:
3
,
'om'
:
3
,
'or'
:
3
,
'pa'
:
3
,
'pap'
:
3
,
'pl'
:
13
,
'ps'
:
3
,
'pt'
:
3
,
'rm'
:
3
,
'ro'
:
9
,
'rof'
:
3
,
'ru'
:
11
,
'rwk'
:
3
,
'sah'
:
0
,
'saq'
:
3
,
'se'
:
7
,
'seh'
:
3
,
'ses'
:
0
,
'sg'
:
0
,
'sh'
:
11
,
'shi'
:
19
,
'sk'
:
12
,
'sl'
:
14
,
'sma'
:
7
,
'smi'
:
7
,
'smj'
:
7
,
'smn'
:
7
,
'sms'
:
7
,
'sn'
:
3
,
'so'
:
3
,
'sq'
:
3
,
'sr'
:
11
,
'ss'
:
3
,
'ssy'
:
3
,
'st'
:
3
,
'sv'
:
3
,
'sw'
:
3
,
'syr'
:
3
,
'ta'
:
3
,
'te'
:
3
,
'teo'
:
3
,
'th'
:
0
,
'ti'
:
4
,
'tig'
:
3
,
'tk'
:
3
,
'tl'
:
4
,
'tn'
:
3
,
'to'
:
0
,
'tr'
:
0
,
'ts'
:
3
,
'tzm'
:
22
,
'uk'
:
11
,
'ur'
:
3
,
've'
:
3
,
'vi'
:
0
,
'vun'
:
3
,
'wa'
:
4
,
'wae'
:
3
,
'wo'
:
0
,
'xh'
:
3
,
'xog'
:
3
,
'yo'
:
0
,
'zh'
:
0
,
'zu'
:
3
};
// utility functions for plural rules methods
function
isIn
(
n
,
list
)
{
return
list
.
indexOf
(
n
)
!==
-
1
;
}
function
isBetween
(
n
,
start
,
end
)
{
return
start
<=
n
&&
n
<=
end
;
}
// list of all plural rules methods:
// map an integer to the plural form name to use
var
pluralRules
=
{
'0'
:
function
(
n
)
{
return
'other'
;
},
'1'
:
function
(
n
)
{
if
((
isBetween
((
n
%
100
),
3
,
10
)))
return
'few'
;
if
(
n
===
0
)
return
'zero'
;
if
((
isBetween
((
n
%
100
),
11
,
99
)))
return
'many'
;
if
(
n
==
2
)
return
'two'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'2'
:
function
(
n
)
{
if
(
n
!==
0
&&
(
n
%
10
)
===
0
)
return
'many'
;
if
(
n
==
2
)
return
'two'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'3'
:
function
(
n
)
{
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'4'
:
function
(
n
)
{
if
((
isBetween
(
n
,
0
,
1
)))
return
'one'
;
return
'other'
;
},
'5'
:
function
(
n
)
{
if
((
isBetween
(
n
,
0
,
2
))
&&
n
!=
2
)
return
'one'
;
return
'other'
;
},
'6'
:
function
(
n
)
{
if
(
n
===
0
)
return
'zero'
;
if
((
n
%
10
)
==
1
&&
(
n
%
100
)
!=
11
)
return
'one'
;
return
'other'
;
},
'7'
:
function
(
n
)
{
if
(
n
==
2
)
return
'two'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'8'
:
function
(
n
)
{
if
((
isBetween
(
n
,
3
,
6
)))
return
'few'
;
if
((
isBetween
(
n
,
7
,
10
)))
return
'many'
;
if
(
n
==
2
)
return
'two'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'9'
:
function
(
n
)
{
if
(
n
===
0
||
n
!=
1
&&
(
isBetween
((
n
%
100
),
1
,
19
)))
return
'few'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'10'
:
function
(
n
)
{
if
((
isBetween
((
n
%
10
),
2
,
9
))
&&
!
(
isBetween
((
n
%
100
),
11
,
19
)))
return
'few'
;
if
((
n
%
10
)
==
1
&&
!
(
isBetween
((
n
%
100
),
11
,
19
)))
return
'one'
;
return
'other'
;
},
'11'
:
function
(
n
)
{
if
((
isBetween
((
n
%
10
),
2
,
4
))
&&
!
(
isBetween
((
n
%
100
),
12
,
14
)))
return
'few'
;
if
((
n
%
10
)
===
0
||
(
isBetween
((
n
%
10
),
5
,
9
))
||
(
isBetween
((
n
%
100
),
11
,
14
)))
return
'many'
;
if
((
n
%
10
)
==
1
&&
(
n
%
100
)
!=
11
)
return
'one'
;
return
'other'
;
},
'12'
:
function
(
n
)
{
if
((
isBetween
(
n
,
2
,
4
)))
return
'few'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'13'
:
function
(
n
)
{
if
((
isBetween
((
n
%
10
),
2
,
4
))
&&
!
(
isBetween
((
n
%
100
),
12
,
14
)))
return
'few'
;
if
(
n
!=
1
&&
(
isBetween
((
n
%
10
),
0
,
1
))
||
(
isBetween
((
n
%
10
),
5
,
9
))
||
(
isBetween
((
n
%
100
),
12
,
14
)))
return
'many'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'14'
:
function
(
n
)
{
if
((
isBetween
((
n
%
100
),
3
,
4
)))
return
'few'
;
if
((
n
%
100
)
==
2
)
return
'two'
;
if
((
n
%
100
)
==
1
)
return
'one'
;
return
'other'
;
},
'15'
:
function
(
n
)
{
if
(
n
===
0
||
(
isBetween
((
n
%
100
),
2
,
10
)))
return
'few'
;
if
((
isBetween
((
n
%
100
),
11
,
19
)))
return
'many'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'16'
:
function
(
n
)
{
if
((
n
%
10
)
==
1
&&
n
!=
11
)
return
'one'
;
return
'other'
;
},
'17'
:
function
(
n
)
{
if
(
n
==
3
)
return
'few'
;
if
(
n
===
0
)
return
'zero'
;
if
(
n
==
6
)
return
'many'
;
if
(
n
==
2
)
return
'two'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'18'
:
function
(
n
)
{
if
(
n
===
0
)
return
'zero'
;
if
((
isBetween
(
n
,
0
,
2
))
&&
n
!==
0
&&
n
!=
2
)
return
'one'
;
return
'other'
;
},
'19'
:
function
(
n
)
{
if
((
isBetween
(
n
,
2
,
10
)))
return
'few'
;
if
((
isBetween
(
n
,
0
,
1
)))
return
'one'
;
return
'other'
;
},
'20'
:
function
(
n
)
{
if
((
isBetween
((
n
%
10
),
3
,
4
)
||
((
n
%
10
)
==
9
))
&&
!
(
isBetween
((
n
%
100
),
10
,
19
)
||
isBetween
((
n
%
100
),
70
,
79
)
||
isBetween
((
n
%
100
),
90
,
99
)
))
return
'few'
;
if
((
n
%
1000000
)
===
0
&&
n
!==
0
)
return
'many'
;
if
((
n
%
10
)
==
2
&&
!
isIn
((
n
%
100
),
[
12
,
72
,
92
]))
return
'two'
;
if
((
n
%
10
)
==
1
&&
!
isIn
((
n
%
100
),
[
11
,
71
,
91
]))
return
'one'
;
return
'other'
;
},
'21'
:
function
(
n
)
{
if
(
n
===
0
)
return
'zero'
;
if
(
n
==
1
)
return
'one'
;
return
'other'
;
},
'22'
:
function
(
n
)
{
if
((
isBetween
(
n
,
0
,
1
))
||
(
isBetween
(
n
,
11
,
99
)))
return
'one'
;
return
'other'
;
},
'23'
:
function
(
n
)
{
if
((
isBetween
((
n
%
10
),
1
,
2
))
||
(
n
%
20
)
===
0
)
return
'one'
;
return
'other'
;
},
'24'
:
function
(
n
)
{
if
((
isBetween
(
n
,
3
,
10
)
||
isBetween
(
n
,
13
,
19
)))
return
'few'
;
if
(
isIn
(
n
,
[
2
,
12
]))
return
'two'
;
if
(
isIn
(
n
,
[
1
,
11
]))
return
'one'
;
return
'other'
;
}
};
// return a function that gives the plural form name for a given integer
var
index
=
locales2rules
[
lang
.
replace
(
/-.*$/
,
''
)];
if
(
!
(
index
in
pluralRules
))
{
consoleWarn
(
'plural form unknown for ['
+
lang
+
']'
);
return
function
()
{
return
'other'
;
};
}
return
pluralRules
[
index
];
}
// pre-defined 'plural' macro
gMacros
.
plural
=
function
(
str
,
param
,
key
,
prop
)
{
var
n
=
parseFloat
(
param
);
if
(
isNaN
(
n
))
return
str
;
// TODO: support other properties (l20n still doesn't...)
if
(
prop
!=
gTextProp
)
return
str
;
// initialize _pluralRules
if
(
!
gMacros
.
_pluralRules
)
gMacros
.
_pluralRules
=
getPluralRules
(
gLanguage
);
var
index
=
'['
+
gMacros
.
_pluralRules
(
n
)
+
']'
;
// try to find a [zero|one|two] key if it's defined
if
(
n
===
0
&&
(
key
+
'[zero]'
)
in
gL10nData
)
{
str
=
gL10nData
[
key
+
'[zero]'
][
prop
];
}
else
if
(
n
==
1
&&
(
key
+
'[one]'
)
in
gL10nData
)
{
str
=
gL10nData
[
key
+
'[one]'
][
prop
];
}
else
if
(
n
==
2
&&
(
key
+
'[two]'
)
in
gL10nData
)
{
str
=
gL10nData
[
key
+
'[two]'
][
prop
];
}
else
if
((
key
+
index
)
in
gL10nData
)
{
str
=
gL10nData
[
key
+
index
][
prop
];
}
return
str
;
};
/**
* l10n dictionary functions
*/
// fetch an l10n object, warn if not found, apply `args' if possible
function
getL10nData
(
key
,
args
,
fallback
)
{
var
data
=
gL10nData
[
key
];
if
(
!
data
)
{
consoleWarn
(
'#'
+
key
+
' missing for ['
+
gLanguage
+
']'
);
if
(
!
fallback
)
{
return
null
;
}
data
=
fallback
;
}
/** This is where l10n expressions should be processed.
* The plan is to support C-style expressions from the l20n project;
* until then, only two kinds of simple expressions are supported:
* {[ index ]} and {{ arguments }}.
*/
var
rv
=
{};
for
(
var
prop
in
data
)
{
var
str
=
data
[
prop
];
str
=
substIndexes
(
str
,
args
,
key
,
prop
);
str
=
substArguments
(
str
,
args
);
rv
[
prop
]
=
str
;
}
return
rv
;
}
// replace {[macros]} with their values
function
substIndexes
(
str
,
args
,
key
,
prop
)
{
var
reIndex
=
/\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/
;
var
reMatch
=
reIndex
.
exec
(
str
);
if
(
!
reMatch
||
!
reMatch
.
length
)
return
str
;
// an index/macro has been found
// Note: at the moment, only one parameter is supported
var
macroName
=
reMatch
[
1
];
var
paramName
=
reMatch
[
2
];
var
param
;
if
(
args
&&
paramName
in
args
)
{
param
=
args
[
paramName
];
}
else
if
(
paramName
in
gL10nData
)
{
param
=
gL10nData
[
paramName
];
}
// there's no macro parser yet: it has to be defined in gMacros
if
(
macroName
in
gMacros
)
{
var
macro
=
gMacros
[
macroName
];
str
=
macro
(
str
,
param
,
key
,
prop
);
}
return
str
;
}
// replace {{arguments}} with their values
function
substArguments
(
str
,
args
)
{
var
reArgs
=
/\{\{\s*([a-zA-Z\.]+)\s*\}\}/
;
var
match
=
reArgs
.
exec
(
str
);
while
(
match
)
{
if
(
!
match
||
match
.
length
<
2
)
return
str
;
// argument key not found
var
arg
=
match
[
1
];
var
sub
=
''
;
if
(
arg
in
args
)
{
sub
=
args
[
arg
];
}
else
if
(
arg
in
gL10nData
)
{
sub
=
gL10nData
[
arg
][
gTextProp
];
}
else
{
consoleWarn
(
'could not find argument {{'
+
arg
+
'}}'
);
return
str
;
}
str
=
str
.
substring
(
0
,
match
.
index
)
+
sub
+
str
.
substr
(
match
.
index
+
match
[
0
].
length
);
match
=
reArgs
.
exec
(
str
);
}
return
str
;
}
// translate an HTML element
function
translateElement
(
element
)
{
var
l10n
=
getL10nAttributes
(
element
);
if
(
!
l10n
.
id
)
return
;
// get the related l10n object
var
data
=
getL10nData
(
l10n
.
id
,
l10n
.
args
);
if
(
!
data
)
{
consoleWarn
(
'#'
+
l10n
.
id
+
' missing for ['
+
gLanguage
+
']'
);
return
;
}
// translate element (TODO: security checks?)
// for the node content, replace the content of the first child textNode
// and clear other child textNodes
if
(
data
[
gTextProp
])
{
// XXX
if
(
element
.
children
.
length
===
0
)
{
element
[
gTextProp
]
=
data
[
gTextProp
];
}
else
{
var
children
=
element
.
childNodes
,
found
=
false
;
for
(
var
i
=
0
,
l
=
children
.
length
;
i
<
l
;
i
++
)
{
if
(
children
[
i
].
nodeType
===
3
&&
/\S/
.
test
(
children
[
i
].
textContent
))
{
// XXX
// using nodeValue seems cross-browser
if
(
found
)
{
children
[
i
].
nodeValue
=
''
;
}
else
{
children
[
i
].
nodeValue
=
data
[
gTextProp
];
found
=
true
;
}
}
}
if
(
!
found
)
{
consoleWarn
(
'unexpected error, could not translate element content'
);
}
}
delete
data
[
gTextProp
];
}
for
(
var
k
in
data
)
{
element
[
k
]
=
data
[
k
];
}
}
// translate an HTML subtree
function
translateFragment
(
element
)
{
element
=
element
||
document
.
documentElement
;
// check all translatable children (= w/ a `data-l10n-id' attribute)
var
children
=
getTranslatableChildren
(
element
);
var
elementCount
=
children
.
length
;
for
(
var
i
=
0
;
i
<
elementCount
;
i
++
)
{
translateElement
(
children
[
i
]);
}
// translate element itself if necessary
translateElement
(
element
);
}
// cross-browser API (sorry, oldIE doesn't support getters & setters)
return
{
// get a localized string
get
:
function
(
key
,
args
,
fallback
)
{
var
data
=
getL10nData
(
key
,
args
,
{
textContent
:
fallback
});
if
(
data
)
{
// XXX double-check this
return
'textContent'
in
data
?
data
.
textContent
:
''
;
}
return
'{{'
+
key
+
'}}'
;
},
// debug
getData
:
function
()
{
return
gL10nData
;
},
getText
:
function
()
{
return
gTextData
;
},
// get|set the document language
getLanguage
:
function
()
{
return
gLanguage
;
},
setLanguage
:
function
(
lang
)
{
loadLocale
(
lang
,
translateFragment
);
},
// get the direction (ltr|rtl) of the current language
getDirection
:
function
()
{
// http://www.w3.org/International/questions/qa-scripts
// Arabic, Hebrew, Farsi, Pashto, Urdu
var
rtlList
=
[
'ar'
,
'he'
,
'fa'
,
'ps'
,
'ur'
];
return
(
rtlList
.
indexOf
(
gLanguage
)
>=
0
)
?
'rtl'
:
'ltr'
;
},
// translate an element or document fragment
translate
:
translateFragment
,
// this can be used to prevent race conditions
getReadyState
:
function
()
{
return
gReadyState
;
}
};
})
(
window
,
document
);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 3:27 AM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ca/c2/b84ef8b744aac1669c546bff60f8
Default Alt Text
l10n.js (24 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline