" ).append( getPopoverHtml() ).html(),
34 | onload: popoverOnload,
35 | translationPair: translationPair
36 | } );
37 | },
38 | getTranslationHtml: function() {
39 | form = getInputForm( translationPair );
40 | return getPopoverHtml();
41 | }
42 | };
43 | }
44 |
45 | function popoverOnload( el ) {
46 | jQuery( el ).find( 'textarea' ).eq( 0 ).focus();
47 | }
48 |
49 |
50 | function getOriginalHtml( translationPair ) {
51 | var originalHtml,
52 | plural = translationPair.getOriginal().getPlural();
53 | if ( plural ) {
54 | originalHtml = 'Singular:
' + originalHtml );
61 | originalHtml.find( 'strong.singular' ).text( translationPair.getOriginal().getSingular() );
62 |
63 | if ( plural ) {
64 | originalHtml.find( 'strong.plural' ).text( plural );
65 | }
66 |
67 | return originalHtml;
68 | }
69 |
70 | function getInputForm( translationPair ) {
71 | // TODO: add input checking and bail for empty or unexpected values
72 |
73 | var form = getHtmlTemplate( 'new-translation' ).clone(),
74 | original = form.find( 'div.original' ),
75 | pair = form.find( 'div.pair' ),
76 | pairs = form.find( 'div.pairs' ),
77 | item;
78 |
79 | original.html( getOriginalHtml( translationPair ) );
80 |
81 | if ( translationPair.getContext() ) {
82 | form.find( 'p.context' ).text( translationPair.getContext() ).show();
83 | }
84 |
85 | if ( translationPair.getOriginal().getComment() ) {
86 | form.find( 'p.comment' ).text( translationPair.getOriginal().getComment() ).show();
87 | }
88 |
89 | item = translationPair.getTranslation().getTextItems();
90 | for ( var i = 0; i < item.length; i++ ) {
91 | if ( i > 0 ) {
92 | pair = pair.eq( 0 ).clone();
93 | }
94 |
95 | pair.find( 'p' ).text( item[ i ].getCaption() );
96 | pair.find( 'textarea' ).text( item[ i ].getText() ).attr( 'placeholder', 'Could you help us and translate this to ' + locale.getLanguageName() + '? Thanks!' );
97 |
98 | if ( i > 0 ) {
99 | pairs.append( pair );
100 | }
101 | }
102 |
103 | return form;
104 | }
105 |
106 | function getOverview( translationPair ) {
107 | // TODO: add input checking and bail for empty or unexpected values
108 |
109 | var form = getHtmlTemplate( 'existing-translation' ).clone(),
110 | original = form.find( 'div.original' ),
111 | pair = form.find( 'div.pair' ),
112 | pairs = form.find( 'div.pairs' ),
113 | item, description;
114 |
115 | original.html( getOriginalHtml( translationPair ) );
116 |
117 | if ( translationPair.getContext() ) {
118 | form.find( 'p.context' ).text( translationPair.getContext() ).show();
119 | }
120 |
121 | if ( translationPair.getOriginal().getComment() ) {
122 | form.find( 'p.comment' ).text( translationPair.getOriginal().getComment() ).show();
123 | }
124 |
125 | item = translationPair.getTranslation().getTextItems();
126 | for ( var i = 0; i < item.length; i++ ) {
127 | if ( i > 0 ) {
128 | pair = pair.eq( 0 ).clone();
129 | }
130 |
131 | description = item[ i ].getInfoText();
132 | if ( description !== '' ) {
133 | pair.find( 'span.type' ).text( description + ': ' );
134 | }
135 | pair.find( 'span.translation' ).text( item[ i ].getText() );
136 | if ( i > 0 ) {
137 | pairs.append( pair );
138 | }
139 | }
140 |
141 | return form;
142 | }
143 |
144 | function getHtmlTemplate( popoverType ) {
145 | switch ( popoverType ) {
146 | case 'existing-translation':
147 | return jQuery(
148 | '
'
163 | );
164 |
165 | case 'new-translation':
166 | return jQuery(
167 | '
'
181 | );
182 | }
183 | }
184 |
185 | module.exports = Popover;
186 |
--------------------------------------------------------------------------------
/lib/translation-pair.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TranslationPair module
3 | */
4 | var Original = require( './original' ),
5 | Translation = require( './translation' ),
6 | Popover = require( './popover' ),
7 | translationData;
8 |
9 | function TranslationPair( locale, original, context, translation ) {
10 | var translations = [], selectedTranslation, glotPressProject,
11 | screenText = false;
12 |
13 | if ( 'object' !== typeof original || original.type !== 'Original' ) {
14 | original = new Original( original );
15 | }
16 |
17 | if ( 'object' === typeof translation ) {
18 |
19 | if ( translation.type !== 'Translation' ) {
20 | translation = new Translation( locale, translation );
21 | }
22 |
23 | translations.push( translation );
24 | } else {
25 | translation = original.getEmptyTranslation( locale );
26 | }
27 |
28 | selectedTranslation = translation;
29 |
30 | function addTranslation( translation ) {
31 | if ( 'object' !== typeof translation || translation.type !== 'Translation' ) {
32 | translation = new Translation( locale, translation.slice() );
33 | }
34 |
35 | if ( selectedTranslation.getTextItems().length !== translation.getTextItems().length ) {
36 | // translations have to match the existing number of translation items ( singular = 1, plural = dependent on language )
37 | return false;
38 | }
39 |
40 | translations.push( translation );
41 | selectedTranslation = translation;
42 | }
43 |
44 | function loadTranslations( newTranslations ) {
45 | var i, j, t, translation;
46 |
47 | translations = [];
48 |
49 | for ( i = 0; i < newTranslations.length; i++ ) {
50 | translation = [];
51 | for ( j = 0; ( t = newTranslations[ i ][ 'translation_' + j ] ); j++ ) {
52 | translation.push( t );
53 | }
54 | translation = new Translation( locale, translation.slice(), newTranslations[ i ] );
55 | addTranslation( translation );
56 | }
57 |
58 | }
59 |
60 | function sortTranslationsByDate() {
61 | if ( translations.length <= 1 ) {
62 | return;
63 | }
64 |
65 | translations.sort( function( a, b ) {
66 | return b.getComparableDate() - a.getComparableDate();
67 | } );
68 | }
69 |
70 | function setSelectedTranslation( currentUserId ) {
71 |
72 | if ( 'number' === typeof currentUserId ) {
73 | currentUserId = currentUserId.toString();
74 | }
75 |
76 | sortTranslationsByDate();
77 |
78 | for ( var i = 0; i < translations.length; i++ ) {
79 | if ( translations[ i ].getUserId() === currentUserId && translations[ i ].getStatus() ) {
80 | selectedTranslation = translations[ i ];
81 | return;
82 | }
83 |
84 | if ( translations[ i ].isCurrent() ) {
85 | selectedTranslation = translations[ i ];
86 | }
87 | }
88 | }
89 |
90 | function setGlotPressProject( project ) {
91 | return ( glotPressProject = project );
92 | }
93 |
94 | return {
95 | type: 'TranslationPair',
96 | createPopover: function( enclosingNode, glotPress ) {
97 | var popover = new Popover( this, locale, glotPress );
98 | popover.attachTo( enclosingNode );
99 | },
100 | isFullyTranslated: function() {
101 | return selectedTranslation.isFullyTranslated();
102 | },
103 | isTranslationWaiting: function() {
104 | return selectedTranslation.isWaiting();
105 | },
106 | getOriginal: function() {
107 | return original;
108 | },
109 | getContext: function() {
110 | return context;
111 | },
112 | getLocale: function() {
113 | return locale;
114 | },
115 | getScreenText: function() {
116 | return screenText;
117 | },
118 | setScreenText: function( _screenText ) {
119 | screenText = _screenText;
120 | },
121 | getTranslation: function() {
122 | return selectedTranslation;
123 | },
124 | getGlotPressProject: function() {
125 | return glotPressProject;
126 | },
127 | updateAllTranslations: function( newTranslations, currentUserId ) {
128 | if ( ! loadTranslations( newTranslations ) ) {
129 | return false;
130 | }
131 |
132 | if ( 'undefined' === typeof currentUserId ) {
133 | setSelectedTranslation( currentUserId );
134 | }
135 | },
136 | serialize: function() {
137 | // the parameters as array
138 | return {
139 | singular: original.getSingular(),
140 | plural: original.getPlural(),
141 | context: context,
142 | translations: selectedTranslation.serialize(),
143 | key: original.generateJsonHash( context )
144 | };
145 | },
146 | fetchOriginalAndTranslations: function( glotPress, currentUserId ) {
147 | var promise, sendContext;
148 | promise = original.fetchIdAndTranslations( glotPress, context )
149 | .done( function( data ) {
150 |
151 | if ( 'undefined' === typeof data.translations ) {
152 | return;
153 | }
154 |
155 | loadTranslations( data.translations );
156 | setSelectedTranslation( currentUserId );
157 |
158 | if ( typeof data.project !== 'undefined' ) {
159 | setGlotPressProject( data.project );
160 | }
161 |
162 | } );
163 | return promise;
164 | }
165 | };
166 | }
167 |
168 | function extractFromDataElement( dataElement ) {
169 | var translationPair,
170 | original = {
171 | singular: dataElement.data( 'singular' )
172 | };
173 |
174 | if ( dataElement.data( 'plural' ) ) {
175 | original.plural = dataElement.data( 'plural' );
176 | }
177 |
178 | if ( dataElement.data( 'context' ) ) {
179 | original.context = dataElement.data( 'context' );
180 | }
181 |
182 | translationPair = new TranslationPair( translationData.locale, original, original.context );
183 | translationPair.setScreenText( dataElement.text() );
184 |
185 | return translationPair;
186 | }
187 |
188 | function trim( text ) {
189 | if ( typeof text === 'undefined' ) {
190 | return '';
191 | }
192 | return text.replace( /(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '' );
193 | }
194 |
195 | function extractWithStringsUsedOnPage( enclosingNode ) {
196 |
197 | var text, textWithoutSiblings, enclosingNodeWithoutSiblings, context;
198 | if (
199 | typeof translationData.stringsUsedOnPage !== 'object' ||
200 | // not meant to be translatable:
201 | enclosingNode.is( 'style,script' ) ||
202 | enclosingNode.closest( '#querylist' ).length
203 | ) {
204 | return false;
205 | }
206 |
207 | if ( enclosingNode.is( '[data-i18n-context]' ) ) {
208 | context = enclosingNode.data( 'i18n-context' );
209 | } else {
210 | context = enclosingNode.closest( '[data-i18n-context]' );
211 | if ( context.length ) {
212 | context = context.data( 'i18n-context' );
213 | } else {
214 | context = false;
215 | }
216 | }
217 |
218 | translationPair = getTranslationPairForTextUsedOnPage( enclosingNode, context );
219 |
220 | if ( false === translationPair ) {
221 | // remove adjescent nodes for text that is used without immidiately surrounding tag
222 | enclosingNode = enclosingNode.clone( true );
223 | textWithoutSiblings = trim( enclosingNode.find( '*' ).remove().end().text() );
224 | if ( text !== textWithoutSiblings ) {
225 | translationPair = getTranslationPairForTextUsedOnPage( enclosingNode, context );
226 | }
227 | }
228 |
229 | return translationPair;
230 | }
231 |
232 | function anyChildMatches( node, regex ) {
233 | var i, children;
234 |
235 | if ( typeof regex === 'string' ) {
236 | regex = new RegExp( regex );
237 | }
238 |
239 | if ( regex instanceof RegExp ) {
240 | children = node.children();
241 | for ( i = 0; i < children.length; i++ ) {
242 | if ( regex.test( children[ i ].innerHTML ) ||
243 | regex.test( children[ i ].textContent ) ) {
244 | return true;
245 | }
246 | }
247 | }
248 |
249 | return false;
250 | }
251 |
252 | function getTranslationPairForTextUsedOnPage( node, context ) {
253 | var original, placeholderRegex,
254 | contexts,
255 | translationPairs,
256 | translationPair, newPlaceholder,
257 | entry = false,
258 | nodeText, nodeHtml;
259 |
260 | nodeText = trim( node.text() );
261 |
262 | if ( ! nodeText.length || nodeText.length > 3000 ) {
263 | return false;
264 | }
265 |
266 | if ( typeof translationData.stringsUsedOnPage[ nodeText ] !== 'undefined' ) {
267 | entry = translationData.stringsUsedOnPage[ nodeText ];
268 |
269 | context = entry[ 1 ];
270 | if ( typeof context !== 'undefined' && context && context.length === 1 ) {
271 | context = context[ 0 ];
272 | }
273 | translationPair = new TranslationPair( translationData.locale, entry[ 0 ], context );
274 | translationPair.setScreenText( nodeText );
275 |
276 | return translationPair;
277 | }
278 |
279 | // html to support translate( '
Translatable Text ' )
280 | nodeHtml = trim( node.html() );
281 |
282 | for ( i = 0; i < translationData.placeholdersUsedOnPage.length; i++ ) {
283 | entry = translationData.placeholdersUsedOnPage[ i ];
284 |
285 | if ( entry.regex.test( nodeHtml ) ) {
286 |
287 | // We want the innermost node that matches, so
288 | if ( anyChildMatches( node, entry.regex ) ) {
289 | continue;
290 | }
291 |
292 | translationPair = new TranslationPair( translationData.locale, entry.original, entry.context );
293 | translationPair.setScreenText( nodeText );
294 |
295 | return translationPair;
296 | }
297 | }
298 |
299 | return false;
300 | }
301 |
302 | TranslationPair.extractFrom = function( enclosingNode ) {
303 |
304 | if ( typeof translationData !== 'object' ) {
305 | return false;
306 | }
307 |
308 | if ( enclosingNode.is( 'data.translatable' ) ) {
309 | return extractFromDataElement( enclosingNode );
310 | }
311 |
312 | if ( enclosingNode.closest( 'data.translatable' ).length ) {
313 | return extractFromDataElement( enclosingNode.closest( 'data.translatable' ) );
314 | }
315 |
316 | return extractWithStringsUsedOnPage( enclosingNode );
317 | };
318 |
319 | TranslationPair.setTranslationData = function( newTranslationData ) {
320 | var key, entry, context,
321 | placeholdersUsedOnPage = [];
322 |
323 | translationData = newTranslationData;
324 |
325 | // convert regular expressions to RegExp objects for later use
326 | if ( typeof translationData.placeholdersUsedOnPage === 'object' ) {
327 | for ( key in translationData.placeholdersUsedOnPage ) {
328 | entry = translationData.placeholdersUsedOnPage[ key ];
329 |
330 | if ( typeof entry.regex === 'undefined' ) {
331 | entry = {
332 | original: entry[ 0 ],
333 | regex: new RegExp( '^\\s*' + entry[ 1 ] + '\\s*$' ),
334 | context: entry[ 2 ]
335 | };
336 | }
337 | placeholdersUsedOnPage.push( entry );
338 | }
339 | }
340 | translationData.placeholdersUsedOnPage = placeholdersUsedOnPage;
341 | };
342 |
343 | TranslationPair._test = {
344 | anyChildMatches: anyChildMatches
345 | };
346 |
347 | module.exports = TranslationPair;
348 |
--------------------------------------------------------------------------------
/lib/translation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translation module
3 | */
4 | function Translation( locale, items, glotPressMetadata ) {
5 | var Item, i, status, translationId, userId, dateAdded,
6 | dateAddedUnixTimestamp = 0;
7 |
8 | if ( 'object' === typeof glotPressMetadata ) {
9 | if ( 'undefined' !== glotPressMetadata.status ) {
10 | status = glotPressMetadata.status;
11 | }
12 | if ( 'undefined' !== glotPressMetadata.translation_id ) {
13 | translationId = glotPressMetadata.translation_id;
14 | }
15 | if ( 'undefined' !== glotPressMetadata.user_id ) {
16 | userId = glotPressMetadata.user_id;
17 | }
18 | if ( 'undefined' !== glotPressMetadata.date_added ) {
19 | dateAdded = glotPressMetadata.date_added;
20 | }
21 | }
22 |
23 | if ( 'string' !== typeof status ) {
24 | status = 'current';
25 | }
26 |
27 | if ( isNaN( translationId ) ) {
28 | translationId = false;
29 | }
30 |
31 | if ( isNaN( userId ) ) {
32 | userId = false;
33 | }
34 |
35 | if ( dateAdded ) {
36 | dateAddedUnixTimestamp = getUnixTimestamp( dateAdded );
37 | }
38 |
39 | function getUnixTimestamp( mysqlDate ) {
40 | var dateParts = mysqlDate.split( '-' );
41 | var timeParts = dateParts[ 2 ].substr( 3 ).split( ':' );
42 |
43 | return new Date(
44 | dateParts[ 0 ],
45 | dateParts[ 1 ] - 1,
46 | dateParts[ 2 ].substr( 0, 2 ),
47 | timeParts[ 0 ],
48 | timeParts[ 1 ],
49 | timeParts[ 2 ]
50 | );
51 | }
52 |
53 | Item = function( i, text ) {
54 | return {
55 | isTranslated: function() {
56 | return text.length > 0;
57 | },
58 | getCaption: function() {
59 | var numbers;
60 |
61 | if ( items.length === 1 ) {
62 | return "";
63 | }
64 |
65 | if ( items.length === 2 ) {
66 | if ( i === 0 ) {
67 | return "Singular";
68 | }
69 | return "Plural";
70 | }
71 |
72 | numbers = locale.getNumbersForIndex( i );
73 |
74 | if ( numbers.length ) {
75 | return "For numbers like: " + numbers.join( ", " );
76 | }
77 |
78 | return "";
79 | },
80 | getInfoText: function() {
81 | var numbers;
82 |
83 | if ( items.length === 1 ) {
84 | return "";
85 | }
86 |
87 | if ( items.length === 2 ) {
88 | if ( i === 0 ) {
89 | return "Singular";
90 | }
91 | return "Plural";
92 | }
93 |
94 | numbers = locale.getNumbersForIndex( i );
95 |
96 | if ( numbers.length ) {
97 | return numbers.join( ", " );
98 | }
99 |
100 | return "";
101 | },
102 | getText: function() {
103 | return text;
104 | }
105 | };
106 | };
107 |
108 | if ( 'object' !== typeof items || 'number' !== typeof items.length ) {
109 | return false;
110 | }
111 |
112 | for ( i = 0; i < items.length; i++ ) {
113 | items[ i ] = new Item( i, items[ i ] );
114 | }
115 |
116 | return {
117 | type: 'Translation',
118 | isFullyTranslated: function() {
119 | var i;
120 |
121 | for ( i = 0; i < items.length; i++ ) {
122 | if ( false === items[ i ].isTranslated() ) {
123 | return false;
124 | }
125 | }
126 | return true;
127 | },
128 | isCurrent: function() {
129 | return 'current' === status;
130 | },
131 | isWaiting: function() {
132 | return 'waiting' === status;
133 | },
134 | getStatus: function() {
135 | return status;
136 | },
137 | getDate: function() {
138 | return dateAdded;
139 | },
140 | getComparableDate: function() {
141 | return dateAddedUnixTimestamp;
142 | },
143 | getUserId: function() {
144 | return userId;
145 | },
146 | getTextItems: function() {
147 | return items;
148 | },
149 | serialize: function() {
150 | var i,
151 | serializedItems = [];
152 |
153 | for ( i = 0; i < items.length; i++ ) {
154 | serializedItems.push( items[ i ].getText() );
155 | }
156 | return serializedItems;
157 | }
158 | };
159 | }
160 |
161 | module.exports = Translation;
162 |
--------------------------------------------------------------------------------
/lib/walker.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function( TranslationPair, jQuery, document ) {
3 | return {
4 | walkTextNodes: function( origin, callback, finishedCallback ) {
5 | var node, walker;
6 |
7 | if ( typeof document === 'object' ) {
8 | walker = document.createTreeWalker( origin, NodeFilter.SHOW_TEXT, null, false );
9 |
10 | while ( ( node = walker.nextNode() ) ) {
11 | walk( node );
12 | }
13 | } else {
14 | jQuery( origin ).find( '*' ).contents().filter( function() {
15 | return this.nodeType === 3; // Node.TEXT_NODE
16 | } ).each( function() {
17 | walk( this );
18 | } );
19 | }
20 |
21 | if ( typeof finishedCallback === 'function' ) {
22 | finishedCallback();
23 | }
24 |
25 | function walk( textNode ) {
26 | var translationPair,
27 | enclosingNode = jQuery( textNode.parentNode );
28 |
29 | if (
30 | enclosingNode.is( 'script' ) ||
31 | enclosingNode.hasClass( 'translator-checked' )
32 | ) {
33 | return false;
34 | }
35 |
36 | enclosingNode.addClass( 'translator-checked' );
37 |
38 | if (
39 | enclosingNode.closest( '.webui-popover' ).length
40 | ) {
41 | return false;
42 | }
43 |
44 | translationPair = TranslationPair.extractFrom( enclosingNode );
45 |
46 | if ( false === translationPair ) {
47 | enclosingNode.addClass( 'translator-dont-translate' );
48 | return false;
49 | }
50 |
51 | if ( typeof callback === 'function' ) {
52 | callback( translationPair, enclosingNode );
53 | }
54 |
55 | return true;
56 | }
57 | }
58 | };
59 | };
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "community-translator",
3 | "version": "0.0.1",
4 | "description": "An in-Page translation tool.",
5 | "author": {
6 | "name": "Alex Kirk",
7 | "email": "alex.kirk@automattic.com"
8 | },
9 | "keywords": [],
10 | "dependencies": {
11 | "JSON2": "*",
12 | "debug": ">=2.6.9",
13 | "jed": "*"
14 | },
15 | "devDependencies": {
16 | "better-assert": "*",
17 | "browserify": "*",
18 | "chai": "^2.1.2",
19 | "jquery": "^3.3.1",
20 | "jsdom": "^11.6.2",
21 | "mocha": "*",
22 | "uglify-js": "*",
23 | "uglifyify": "*"
24 | },
25 | "license": "GPL-2.0+",
26 | "scripts": {
27 | "test": "make test"
28 | },
29 | "readme": "",
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/Automattic/community-translator.git"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/batcher.js:
--------------------------------------------------------------------------------
1 | var betterAssert = require( 'better-assert' ),
2 | assert = require( 'assert' ),
3 | Locale = require( '../lib/locale' ),
4 | TranslationPair = require( '../lib/translation-pair' ),
5 | html = '',
6 | jsdom = require( 'jsdom' );
7 |
8 |
9 | // Set up jQuery related global parameters
10 | var dom = new jsdom.JSDOM( html, {
11 | resources: 'usable',
12 | runScripts: 'dangerously',
13 | } );
14 | var jquery = require( 'jquery' )( dom.window );
15 | global.window = dom.window;
16 | global.document = dom.window.document;
17 | global.jQuery = global.$ = jquery;
18 |
19 | // Module level vars that depend on jQuery
20 | var batcher = require( '../lib/batcher.js' ),
21 | batchedTestFunctionFoo = batcher( testFunctionFoo );
22 |
23 | function testFunctionFoo( arrayArg, callback ) {
24 | return ( arrayArg.map( function( v ) {
25 | return v + 'foo';
26 | } ) );
27 | }
28 |
29 | describe( 'Batcher', function() {
30 | // Sanity check.
31 | it( 'depends on jQuery.Deferred', function() {
32 | assert( jQuery );
33 | assert( jQuery.Deferred );
34 | } );
35 |
36 | it( 'returns the same value for a single call',
37 | function() {
38 |
39 | batchedResult = batchedTestFunctionFoo( '1' );
40 |
41 | testFunctionFoo( [ '1' ],
42 | function( originalResult ) {
43 | jQuery.when()
44 | .then( function( result ) {
45 | assert.deepEqual( originalResult[0], result );
46 | } );
47 | } );
48 | } );
49 |
50 | it( 'returns the same values with multiple calls',
51 | function() {
52 | jQuery.when( batchedTestFunctionFoo( 'a' ),
53 | batchedTestFunctionFoo( 'b' ),
54 | batchedTestFunctionFoo( 'c' ),
55 | testFunctionFoo( [ 'a', 'b', 'c' ] ) )
56 | .then( function( a, b, c, originalFunctionResult ) {
57 | assert.deepEqual( [ a, b, c ], originalFunctionResult );
58 | } );
59 | } );
60 |
61 | it( 'fails if given a non-function', function() {
62 | assert( ! batcher( "foo" ) );
63 | } );
64 |
65 | it( 'returns a function when given a function', function() {
66 | assert.equal( 'function', typeof batcher( function() {} ) );
67 | } );
68 |
69 | it( 'produces a function that returns jQuery.Deferred objects',
70 | function() {
71 | // Quick and dirty test for jQuery().Deferred
72 | var isWhenable = function( object ) {
73 | return object && object.then && (
74 | 'function' === typeof object.then );
75 | };
76 |
77 | var result = batchedTestFunctionFoo( "test" );
78 |
79 | assert( isWhenable( batchedTestFunctionFoo( "test" ) ) );
80 | } );
81 |
82 | it( 'calls the original function only once',
83 | function() {
84 | var counter = 0,
85 | batchedCountingFunction = batcher( countingFunction );
86 |
87 | function identity( v ) {
88 | return v;
89 | }
90 |
91 | function countingFunction( arrayArg, callback ) {
92 | counter++;
93 | return arrayArg.map( identity );
94 | }
95 |
96 | assert.equal( counter, 0 );
97 | jQuery.when( batchedCountingFunction( 'a' ),
98 | batchedCountingFunction( 'b' ),
99 | batchedCountingFunction( 'c' ) )
100 | .then( function() {
101 | assert.equal( counter, 1 );
102 | } );
103 | } );
104 | } );
105 |
--------------------------------------------------------------------------------
/test/de.js:
--------------------------------------------------------------------------------
1 | var assert = require('better-assert'),
2 | Locale = require('../lib/locale');
3 | TranslationPair = require('../lib/translation-pair');
4 |
5 | describe('German', function () {
6 | var locale = new Locale( 'de', 'German', 'nplurals=2; plural=n != 1;');
7 | var translationPair = new TranslationPair( locale, ['%(numberOfThings) thing', '%(numberOfThings) things'] );
8 |
9 | it( 'should have 2 plurals', function () {
10 | assert( 2 === translationPair.getTranslation().getTextItems().length);
11 | });
12 |
13 | translationPair.updateAllTranslations( [ [ '1 things', '2 things'] ] );
14 | it( 'should accept a new translation', function () {
15 | assert( 2 === translationPair.getTranslation().getTextItems().length);
16 | });
17 |
18 | translationPair.updateAllTranslations( [ [ '1 thing', '2 things', '3 things' ] ] );
19 | it( 'should not accept a new translation with wrong number of plurals', function () {
20 | assert( 2 === translationPair.getTranslation().getTextItems().length);
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/test/glotpress.js:
--------------------------------------------------------------------------------
1 | var assert = require( 'chai' ).assert,
2 | GlotPress = require( '../lib/glotpress' );
3 |
4 | describe( 'GlotPress', function() {
5 |
6 | var GlotPressInstance;
7 |
8 | describe( 'getPermalink', function() {
9 | var glotPressProject = false,
10 | translationPairMock = {
11 | getGlotPressProject: function() {
12 | return glotPressProject;
13 | },
14 | getOriginal: function() {
15 | return {
16 | getId: function() {
17 | return 123;
18 | }
19 | };
20 | },
21 | };
22 |
23 | before( function( done ) {
24 | GlotPressInstance = new GlotPress( {
25 | getLocaleCode: function() {
26 | return 'en';
27 | }
28 | } );
29 | done();
30 | } );
31 |
32 | it( 'should return the correct default permalink', function() {
33 | GlotPressInstance.loadSettings( {
34 | url: 'https://translate.wordpress.com',
35 | project: 'test',
36 | } );
37 | assert.equal(
38 | GlotPressInstance.getPermalink( translationPairMock ),
39 | 'https://translate.wordpress.com/projects/test/en/default?filters[original_id]=123'
40 | );
41 | } );
42 |
43 | it( 'should return the correct permalink with translation set slug', function() {
44 | GlotPressInstance.loadSettings( {
45 | url: 'https://translate.wordpress.com',
46 | project: 'test',
47 | translation_set_slug: 'formal',
48 | } );
49 | assert.equal(
50 | GlotPressInstance.getPermalink( translationPairMock ),
51 | 'https://translate.wordpress.com/projects/test/en/formal?filters[original_id]=123'
52 | );
53 | } );
54 | it( 'should return the correct permalink with value from getGlotPressProject()', function() {
55 | GlotPressInstance.loadSettings( {
56 | url: 'https://translate.wordpress.com',
57 | project: 'test',
58 | translation_set_slug: 'formal',
59 | } );
60 | glotPressProject = 'wpcom';
61 | assert.equal(
62 | GlotPressInstance.getPermalink( translationPairMock ),
63 | 'https://translate.wordpress.com/projects/wpcom/en/formal?filters[original_id]=123'
64 | );
65 | } );
66 | } );
67 |
68 | } );
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | console.log( 'Running unit tests' );
2 |
3 | require( "./glotpress" );
4 | require( "./translation-pair" );
5 | require( "./string-extraction/translation-pair" );
6 | require( "./de" );
7 | require( "./ru" );
8 | require( "./jp" );
9 | require( "./batcher.js" );
10 |
--------------------------------------------------------------------------------
/test/jp.js:
--------------------------------------------------------------------------------
1 | var assert = require('better-assert'),
2 | Locale = require('../lib/locale');
3 | TranslationPair = require('../lib/translation-pair');
4 |
5 | describe('Japanese', function () {
6 | var locale = new Locale( 'jp', 'Japanese', 'nplurals=1; plural=0;');
7 | var translationPair = new TranslationPair( locale, ['%(numberOfThings) thing', null, '%(numberOfThings) things'] );
8 |
9 | it( 'should have 1 plural', function () {
10 | assert( 1 === translationPair.getTranslation().getTextItems().length);
11 | });
12 |
13 | translationPair.updateAllTranslations( [ ['things'] ] );
14 | it( 'should accept a new translation', function () {
15 | assert( 1 === translationPair.getTranslation().getTextItems().length);
16 | });
17 |
18 | translationPair.updateAllTranslations( [ ['1 thing', '2 things'] ] );
19 | it( 'should not accept a new translation with wrong number of plurals', function () {
20 | assert( 1 === translationPair.getTranslation().getTextItems().length);
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/test/ru.js:
--------------------------------------------------------------------------------
1 | var assert = require('better-assert'),
2 | Locale = require('../lib/locale');
3 | TranslationPair = require('../lib/translation-pair');
4 |
5 | describe('Russian', function () {
6 | var locale = new Locale( 'ru', 'Russian', 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);');
7 | var translationPair = new TranslationPair( locale, ['%(numberOfThings) thing', '%(numberOfThings) things'] );
8 |
9 | it( 'should have 3 plurals', function () {
10 | assert( 3 === translationPair.getTranslation().getTextItems().length);
11 | });
12 |
13 | translationPair.updateAllTranslations( [ [ '1 things', '2 things', 'a few things' ] ] );
14 | it( 'should accept a new translation', function () {
15 | assert( 3 === translationPair.getTranslation().getTextItems().length);
16 | });
17 |
18 | translationPair.updateAllTranslations( [ [ '1 thing', '2 things' ] ] );
19 | it( 'should not accept a new translation with wrong number of plurals', function () {
20 | assert( 3 === translationPair.getTranslation().getTextItems().length);
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/test/string-extraction/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Akismet blocks spam from getting to your blog.
6 | There’s nothing in your spam queue at the moment.
7 |
8 |
Espacio de almacenamiento
9 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
←
28 |
29 | Showing 1 – 5 of 42 unresolved posts
30 |
31 |
→
32 |
33 |
34 |
35 |
36 |
37 |
Hi i18n!
38 |
39 |
40 | 02:56 em 7 abril, 2015 , 7 comentários
41 |
42 |
43 |
44 |
45 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/test/string-extraction/translation-data/en-uk.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "stringsUsedOnPage": {
3 | "Leave a Reply": [ "Leave a Reply", null ],
4 | "Tags": [ "Tags", "taxonomy general name" ],
5 | "taxonomy general name\\u0004Tags": [ "Tags", "taxonomy general name" ],
6 | "Categories": [ "Categories", "taxonomy general name" ],
7 | "taxonomy general name\\u0004Categories": [ "Categories", "taxonomy general name" ],
8 | "Tag": [ "Tag", "taxonomy singular name" ],
9 | "taxonomy singular name\\u0004Tag": [ "Tag", "taxonomy singular name" ],
10 | "Category": [ "Category", "taxonomy singular name" ],
11 | "taxonomy singular name\\u0004Category": [ "Category", "taxonomy singular name" ],
12 | "Search Tags": [ "Search Tags", null ],
13 | "Search Categories": [ "Search Categories", null ],
14 | "Popular Tags": [ "Popular Tags", null ],
15 | "All Tags": [ "All Tags", null ],
16 | "All Categories": [ "All Categories", null ],
17 | "Parent Category": [ "Parent Category", null ],
18 | "Parent Category:": [ "Parent Category:", null ],
19 | "Edit Tag": [ "Edit Tag", null ],
20 | "Edit Category": [ "Edit Category", null ],
21 | "View Tag": [ "View Tag", null ],
22 | "View Category": [ "View Category", null ],
23 | "Update Tag": [ "Update Tag", null ],
24 | "Update Category": [ "Update Category", null ],
25 | "Add New Tag": [ "Add New Tag", null ],
26 | "Add New Category": [ "Add New Category", null ],
27 | "New Tag Name": [ "New Tag Name", null ],
28 | "New Category Name": [ "New Category Name", null ],
29 | "Separate tags with commas": [ "Separate tags with commas", null ],
30 | "Add or remove tags": [ "Add or remove tags", null ],
31 | "Choose from the most used tags": [ "Choose from the most used tags", null ],
32 | "No tags found.": [ "No tags found.", null ],
33 | "No categories found.": [ "No categories found.", null ],
34 | "Background": [ "Background", null ],
35 | "Headings": [ "Headings", null ],
36 | "Links": [ "Links", null ],
37 | "Accent #1": [ "Accent #1", null ],
38 | "Accent #2": [ "Accent #2", null ],
39 | "Header and Sidebar Background Color": [ "Header and Sidebar Background Color", null ],
40 | "Header and Sidebar Link Color": [ "Header and Sidebar Link Color", null ],
41 | "Header and Sidebar Text Color": [ "Header and Sidebar Text Color", null ],
42 | "Yellow": [ "Yellow", null ],
43 | "Pink": [ "Pink", null ],
44 | "Purple": [ "Purple", null ],
45 | "Blue": [ "Blue", null ],
46 | "Dark": [ "Dark", null ],
47 | "Feedback": [ "Feedback", null ],
48 | "Search Feedback": [ "Search Feedback", null ],
49 | "No feedback found": [ "No feedback found", null ],
50 | "Posts": [ "Posts", "post type general name" ],
51 | "post type general name\\u0004Posts": [ "Posts", "post type general name" ],
52 | "Pages": [ "Pages", "post type general name" ],
53 | "post type general name\\u0004Pages": [ "Pages", "post type general name" ],
54 | "Post": [ "Post", "post type singular name" ],
55 | "post type singular name\\u0004Post": [ "Post", "post type singular name" ],
56 | "Page": [ "Page", "post type singular name" ],
57 | "post type singular name\\u0004Page": [ "Page", "post type singular name" ],
58 | "Add New": [ "Add New", "post" ],
59 | "post\\u0004Add New": [ "Add New", "post" ],
60 | "Add a New Post": [ "Add New Post", null ],
61 | "Add New Page": [ "Add New Page", null ],
62 | "Edit Post": [ "Edit Post", null ],
63 | "Edit Page": [ "Edit Page", null ],
64 | "New Post": [ "New Post", null ],
65 | "New Page": [ "New Page", null ],
66 | "View Post": [ "View Post", null ],
67 | "View Page": [ "View Page", null ],
68 | "Search Posts": [ "Search Posts", null ],
69 | "Search Pages": [ "Search Pages", null ],
70 | "No posts found.": [ "No posts found.", null ],
71 | "No pages found.": [ "No pages found.", null ],
72 | "No posts found in Bin.": [ "No posts found in Trash.", null ],
73 | "No pages found in Bin.": [ "No pages found in Trash.", null ],
74 | "Parent Page:": [ "Parent Page:", null ],
75 | "All Posts": [ "All Posts", null ],
76 | "All Pages": [ "All Pages", null ],
77 | "Custom Themes": [ "Custom Themes", null ],
78 | "Custom Theme": [ "Custom Theme", null ],
79 | "All Custom Themes": [ "All Custom Themes", null ],
80 | "Create New Theme": [ "Create New Theme", null ],
81 | "Edit Custom Theme": [ "Edit Custom Theme", null ],
82 | "New Theme": [ "New Theme", null ],
83 | "View Theme": [ "View Theme", null ],
84 | "Custom Themes Archive": [ "Custom Themes Archive", null ],
85 | "Search Custom Themes": [ "Search Custom Themes", null ],
86 | "No Custom Themes found": [ "No Custom Themes found", null ],
87 | "No Custom Themes found in trash": [ "No Custom Themes found in trash", null ],
88 | "Custom Theme:": [ "Custom Theme:", null ],
89 | "Customised Themes": [ "Customized Themes", null ],
90 | "Customised Theme": [ "Customized Theme", null ],
91 | "Cust’d Themes": [ "Cust’d Themes", null ],
92 | "All Customised Themes": [ "All Customized Themes", null ],
93 | "Edit Customised Theme": [ "Edit Customized Theme", null ],
94 | "Customised Themes Archive": [ "Customized Themes Archive", null ],
95 | "Search Customised Themes": [ "Search Customized Themes", null ],
96 | "No Customised Themes found": [ "No Customized Themes found", null ],
97 | "No Customised Themes found in trash": [ "No Customized Themes found in trash", null ],
98 | "Customised Theme:": [ "Customized Theme:", null ],
99 | "No posts to show.": [ "No posts to show.", null ],
100 | "“": [ "“", "opening curly double quote" ],
101 | "opening curly double quote\\u0004“": [ "“", "opening curly double quote" ],
102 | "”": [ "”", "closing curly double quote" ],
103 | "closing curly double quote\\u0004”": [ "”", "closing curly double quote" ],
104 | "’": [ "’", "apostrophe" ],
105 | "apostrophe\\u0004’": [ "’", "apostrophe" ],
106 | "′": [ "′", "prime" ],
107 | "prime\\u0004′": [ "′", "prime" ],
108 | "″": [ "″", "double prime" ],
109 | "double prime\\u0004″": [ "″", "double prime" ],
110 | "‘": [ "‘", "opening curly single quote" ],
111 | "opening curly single quote\\u0004‘": [ "‘", "opening curly single quote" ],
112 | "–": [ "–", "en dash" ],
113 | "en dash\\u0004–": [ "–", "en dash" ],
114 | "—": [ "—", "em dash" ],
115 | "em dash\\u0004—": [ "—", "em dash" ],
116 | "on": [ "on", "Noto Sans font: on or off" ],
117 | "Noto Sans font: on or off\\u0004on": [ "on", "Noto Sans font: on or off" ],
118 | "no-subset": [ "no-subset", "Add new subset (greek, cyrillic, devanagari, vietnamese)" ],
119 | "Add new subset (greek, cyrillic, devanagari, vietnamese)\\u0004no-subset": [ "no-subset", "Add new subset (greek, cyrillic, devanagari, vietnamese)" ],
120 | "expand child menu": [ "expand child menu", null ],
121 | "collapse child menu": [ "collapse child menu", null ],
122 | "Report this content": [ "Report this content", null ],
123 | "Share this:": [ "Share this:", null ],
124 | "»": [ "»", "feed link" ],
125 | "feed link\\u0004»": [ "»", "feed link" ],
126 | "about": [ "about", "Default page slug" ],
127 | "Default page slug\\u0004about": [ "about", "Default page slug" ],
128 | "Subscribe": [ "Subscribe", null ],
129 | "WordPress.com Support": [ "WordPress.com Support", null ],
130 | "WordPress.com Forums": [ "WordPress.com Forums", null ],
131 | "Skip to content": [ "Skip to content", null ],
132 | "Menu and widgets": [ "Menu and widgets", null ],
133 | "Custom": [ "Custom", null ],
134 | "…": [ "…", null ],
135 | "words": [ "words", "word count: words or characters?" ],
136 | "word count: words or characters?\\u0004words": [ "words", "word count: words or characters?" ],
137 | "Pages:": [ "Pages:", null ],
138 | "Next page": [ "Next page", null ],
139 | "Previous page": [ "Previous page", null ],
140 | "Format": [ "Format", "Used before post format." ],
141 | "Used before post format.\\u0004Format": [ "Format", "Used before post format." ],
142 | "Standard": [ "Standard", "Post format" ],
143 | "Post format\\u0004Standard": [ "Standard", "Post format" ],
144 | "Aside": [ "Aside", "Post format" ],
145 | "Post format\\u0004Aside": [ "Aside", "Post format" ],
146 | "Chat": [ "Chat", "Post format" ],
147 | "Post format\\u0004Chat": [ "Chat", "Post format" ],
148 | "Gallery": [ "Gallery", "Post format" ],
149 | "Post format\\u0004Gallery": [ "Gallery", "Post format" ],
150 | "Link": [ "Link", "Post format" ],
151 | "Post format\\u0004Link": [ "Link", "Post format" ],
152 | "Image": [ "Image", "Post format" ],
153 | "Post format\\u0004Image": [ "Image", "Post format" ],
154 | "Quote": [ "Quote", "Post format" ],
155 | "Post format\\u0004Quote": [ "Quote", "Post format" ],
156 | "Status": [ "Status", "Post format" ],
157 | "Post format\\u0004Status": [ "Status", "Post format" ],
158 | "Video": [ "Video", "Post format" ],
159 | "Post format\\u0004Video": [ "Video", "Post format" ],
160 | "Audio": [ "Audio", "Post format" ],
161 | "Post format\\u0004Audio": [ "Audio", "Post format" ],
162 | "Posted on": [ "Posted on", "Used before publish date." ],
163 | "Used before publish date.\\u0004Posted on": [ "Posted on", "Used before publish date." ],
164 | "Leave a comment": [ "Leave a comment", null ],
165 | "1 Comment": [ "1 Comment", null ],
166 | "% Comments": [ "% Comments", null ],
167 | "Comments Off": [ "Comments Off", null ],
168 | "Edit": [ "Edit", null ],
169 | "Previous": [ "Previous", "previous post" ],
170 | "previous post\\u0004Previous": [ "Previous", "previous post" ],
171 | "Next": [ "Next", "next post" ],
172 | "next post\\u0004Next": [ "Next", "next post" ],
173 | "Posts navigation": [ "Posts navigation", null ],
174 | "« Previous": [ "« Previous", null ],
175 | "Next »": [ "Next »", null ],
176 | "https://wordpress.org/": [ "https://wordpress.org/", null ],
177 | "Older posts": [ "Older posts", null ],
178 | "Scroll back to top": [ "Scroll back to top", null ],
179 | "Comment": [ "Comment", null ],
180 | "Post Comment": [ "Post Comment", null ],
181 | "Write a Comment...": [ "Write a Comment...", null ],
182 | "Loading Comments...": [ "Loading Comments...", null ],
183 | "Please be sure to submit some text with your comment.": [ "Please be sure to submit some text with your comment.", null ],
184 | "Please provide an email address to comment.": [ "Please provide an email address to comment.", null ],
185 | "Please provide your name to comment.": [ "Please provide your name to comment.", null ],
186 | "Sorry, but there was an error posting your comment. Please try again later.": [ "Sorry, but there was an error posting your comment. Please try again later.", null ],
187 | "Your comment was approved.": [ "Your comment was approved.", null ],
188 | "Your comment is in moderation.": [ "Your comment is in moderation.", null ],
189 | "Camera": [ "Camera", null ],
190 | "Aperture": [ "Aperture", null ],
191 | "Shutter Speed": [ "Shutter Speed", null ],
192 | "Focal Length": [ "Focal Length", null ],
193 | "Reblog": [ "Reblog", null ],
194 | "Reblogged": [ "Reblogged", null ],
195 | "Add your thoughts here... (optional)": [ "Add your thoughts here... (optional)", null ],
196 | "Reblogging...": [ "Reblogging...", null ],
197 | "Post Reblog": [ "Post Reblog", null ],
198 | "Follow": [ "Follow", null ],
199 | "Unfollow": [ "Unfollow", null ],
200 | "Following": [ "Following", null ],
201 | "Edit the excerpt as needed.": [ "Edit the excerpt as needed.", null ],
202 | "Pick an image to be used as a featured image.": [ "Pick an image to be used as a featured image.", null ],
203 | "Logging In…": [ "Logging In…", null ],
204 | "Posting Comment…": [ "Posting Comment…", null ],
205 | "Log Out": [ "Log Out", null ],
206 | "Log In": [ "Log In", null ],
207 | "Please enter a comment": [ "Please enter a comment", null ],
208 | "Please enter your email address here": [ "Please enter your email address here", null ],
209 | "Invalid email address": [ "Invalid email address", null ],
210 | "Please enter your name here": [ "Please enter your name here", null ],
211 | "This picture will show whenever you leave a comment. Click to customise it.": [ "This picture will show whenever you leave a comment. Click to customize it.", null ],
212 | "Log in to use details from one of these accounts.": [ "Log in to use details from one of these accounts.", null ],
213 | "Change": [ "Change", null ],
214 | "Change Account": [ "Change Account", null ],
215 | "Blog at WordPress.com": [ "Blog at WordPress.com", null ],
216 | "Theme": [ "Theme", null ],
217 | "by": [ "by", null ],
218 | "Learn more about this theme": [ "Learn more about this theme", null ],
219 | "Post to": [ "Post to", null ],
220 | "Cancel": [ "Cancel", null ],
221 | "Reblog Post": [ "Reblog Post", null ],
222 | "Create a free website or blog at WordPress.com": [ "Create a free website or blog at WordPress.com", null ],
223 | "Domingo": [ "Sunday", null ],
224 | "Lunes": [ "Monday", null ],
225 | "Martes": [ "Tuesday", null ],
226 | "Miércoles": [ "Wednesday", null ],
227 | "Jueves": [ "Thursday", null ],
228 | "Viernes": [ "Friday", null ],
229 | "Sábado": [ "Saturday", null ],
230 | "D": [ "S_Sunday_initial", null ],
231 | "L": [ "M_Monday_initial", null ],
232 | "M": [ "T_Tuesday_initial", null ],
233 | "X": [ "W_Wednesday_initial", null ],
234 | "J": [ "T_Thursday_initial", null ],
235 | "V": [ "F_Friday_initial", null ],
236 | "S": [ "S_Saturday_initial", null ],
237 | "Dom": [ "Sun", null ],
238 | "Lun": [ "Mon", null ],
239 | "Mar": [ "Tue", null ],
240 | "Mie": [ "Wed", null ],
241 | "Jue": [ "Thu", null ],
242 | "Vie": [ "Fri", null ],
243 | "Sab": [ "Sat", null ],
244 | "enero": [ "January", null ],
245 | "febrero": [ "February", null ],
246 | "marzo": [ "March", null ],
247 | "abril": [ "April", null ],
248 | "mayo": [ "May", null ],
249 | "junio": [ "June", null ],
250 | "julio": [ "July", null ],
251 | "agosto": [ "August", null ],
252 | "septiembre": [ "September", null ],
253 | "octubre": [ "October", null ],
254 | "noviembre": [ "November", null ],
255 | "diciembre": [ "December", null ],
256 | "ene": [ "Jan_January_abbreviation", null ],
257 | "feb": [ "Feb_February_abbreviation", null ],
258 | "mar": [ "Mar_March_abbreviation", null ],
259 | "abr": [ "Apr_April_abbreviation", null ],
260 | "may": [ "May_May_abbreviation", null ],
261 | "jun": [ "Jun_June_abbreviation", null ],
262 | "jul": [ "Jul_July_abbreviation", null ],
263 | "ago": [ "Aug_August_abbreviation", null ],
264 | "sep": [ "Sep_September_abbreviation", null ],
265 | "oct": [ "Oct_October_abbreviation", null ],
266 | "nov": [ "Nov_November_abbreviation", null ],
267 | "dic": [ "Dec_December_abbreviation", null ],
268 | "am": [ "am", null ],
269 | "pm": [ "pm", null ],
270 | "AM": [ "AM", null ],
271 | "PM": [ "PM", null ],
272 | ".": [ "number_format_thousands_sep", null ],
273 | ",": [ "number_format_decimal_point", null ],
274 | "ltr": [ "ltr", "text direction" ],
275 | "text direction\\u0004ltr": [ "ltr", "text direction" ],
276 | "Mis sitios": [ "My Sites", null ],
277 | "Cambiar sitio": [ "Switch Site", null ],
278 | "Ver sitio": [ "View Site", null ],
279 | "WP Admin": [ "WP Admin", null ],
280 | "Estadísticas": [ "Stats", null ],
281 | "Comentarios": [ "Comments", null ],
282 | "Entradas del blog": [ "Blog Posts", null ],
283 | "Publicar": [ "Publish", "admin bar menu group label" ],
284 | "admin bar menu group label\\u0004Publicar": [ "Publish", "admin bar menu group label" ],
285 | "Añadir": [ "Add", "admin bar menu new item label" ],
286 | "admin bar menu new item label\\u0004Añadir": [ "Add", "admin bar menu new item label" ],
287 | "Aspecto": [ "Look and Feel", "admin bar menu group label" ],
288 | "admin bar menu group label\\u0004Aspecto": [ "Look and Feel", "admin bar menu group label" ],
289 | "Temas": [ "Themes", null ],
290 | "Personalizar": [ "Customize", null ],
291 | "Menús": [ "Menus", null ],
292 | "Configuración": [ "Configuration", null ],
293 | "Compartir": [ "Sharing", null ],
294 | "Usuarios": [ "Users", null ],
295 | "Mejoras": [ "Upgrades", null ],
296 | "Enlace corto": [ "Shortlink", null ],
297 | "Lector": [ "Reader", null ],
298 | "Blogs que sigo": [ "Blogs I Follow", null ],
299 | "Descubrir": [ "Discover", "admin bar menu group label" ],
300 | "admin bar menu group label\\u0004Descubrir": [ "Discover", "admin bar menu group label" ],
301 | "Más recientes": [ "Freshly Pressed", null ],
302 | "Blogs recomendados": [ "Recommended Blogs", null ],
303 | "Buscar amigos": [ "Find Friends", null ],
304 | "Mi Actividad": [ "My Activity", null ],
305 | "Mis Comentarios": [ "My Comments", null ],
306 | "Mis me gusta": [ "My Likes", null ],
307 | "Seguir": [ "Follow", null ],
308 | "Mostrar las visitas del sitio por hora para las últimas 48 horas. Haz clic en todas las estadísticas del sitio.": [ "Showing site views per hour for the last 48 hours. Click for full Site Stats.", null ],
309 | "Denunciar este contenido": [ "Report this content", null ],
310 | "Cerrar sesión": [ "Sign Out", null ],
311 | "Perfil": [ "Profile", null ],
312 | "Configuración de la cuenta": [ "Account Settings", null ],
313 | "Colección de trofeos": [ "Trophy Case", null ],
314 | "Facturación": [ "Billing", null ],
315 | "Añadidos": [ "Extras", null ],
316 | "Ayuda": [ "Help", null ],
317 | "Este blog es de acceso público": [ "This blog is public", null ],
318 | "Blog Links": [ "Blog Links", null ],
319 | "Not currently in the User Showcase": [ "Not currently in the User Showcase", null ],
320 | "Go to Showcase Guidelines": [ "Go to Showcase Guidelines", null ],
321 | "Go to Theme Credits": [ "Go to Theme Credits", null ],
322 | "WordPress.com Showcase": [ "WordPress.com Showcase", null ],
323 | "Email Blog Owner": [ "Email Blog Owner", null ],
324 | "El ID de menú, no puede estar vacio.": [ "The menu ID should not be empty.", null ],
325 | "Blog Id: 8729814": [ "Blog Id: 8729814", null ],
326 | "Blog Dashboard": [ "Blog Dashboard", null ],
327 | "Estadísticas del sitio": [ "Site Stats", null ],
328 | "Entradas": [ "Posts", null ],
329 | "Mis mejoras": [ "My Upgrades", null ],
330 | "Dominios": [ "Domains", null ],
331 | "All Blog Options": [ "All Blog Options", null ],
332 | "Blog's Network Admin": [ "Blog's Network Admin", null ],
333 | "Blog Info": [ "Blog Info", null ],
334 | "Blog Users": [ "Blog Users", null ],
335 | "Blog Themes": [ "Blog Themes", null ],
336 | "Blog Upgrades": [ "Blog Upgrades", null ],
337 | "User's Network Admin": [ "User's Network Admin", null ],
338 | "User's Store Admin": [ "User's Store Admin", null ],
339 | "Load Page Without Cache": [ "Load Page Without Cache", null ],
340 | "Clear Page's Cache": [ "Clear Page's Cache", null ],
341 | "Posts Per Page = 1": [ "Posts Per Page = 1", null ],
342 | "Desactivar Scroll infinito": [ "Disable Infinite Scroll", null ],
343 | "Debug": [ "Debug", null ],
344 | "Ocultar anuncios": [ "Hide adverts", null ],
345 | "Abrir la barra de herramientas": [ "Skip to toolbar", null ],
346 | "Barra de herramientas en la parte superior.": [ "Top navigation toolbar.", null ],
347 | "PHP": [ "PHP", null ],
348 | "DB": [ "DB", null ],
349 | "Memory Usage": [ "Memory Usage", null ],
350 | "Please Enable": [ "Please Enable", null ],
351 | "No": [ "No", null ],
352 | "Total Events": [ "Total Events", null ],
353 | "Doing Cron": [ "Doing Cron", null ],
354 | "Next Event": [ "Next Event", null ],
355 | "Current Time": [ "Current Time", null ],
356 | "Custom Events": [ "Custom Events", null ],
357 | "Next Execution": [ "Next Execution", null ],
358 | "Conexión": [ "Hook", null ],
359 | "Interval Hook": [ "Interval Hook", null ],
360 | "Interval Value": [ "Interval Value", null ],
361 | "Args": [ "Args", null ],
362 | "Schedules": [ "Schedules", null ],
363 | "Interval (S)": [ "Interval (S)", null ],
364 | "Interval (M)": [ "Interval (M)", null ],
365 | "Interval (H)": [ "Interval (H)", null ],
366 | "Display Name": [ "Display Name", null ],
367 | "Cada hora": [ "Once Hourly", null ],
368 | "Dos veces al día": [ "Twice Daily", null ],
369 | "Cada día": [ "Once Daily", null ],
370 | "Core Events": [ "Core Events", null ],
371 | "Sunday": [ "Sunday", null ],
372 | "Monday": [ "Monday", null ],
373 | "Tuesday": [ "Tuesday", null ],
374 | "Wednesday": [ "Wednesday", null ],
375 | "Thursday": [ "Thursday", null ],
376 | "Friday": [ "Friday", null ],
377 | "Saturday": [ "Saturday", null ],
378 | "T": [ "T_Tuesday_initial", null ],
379 | "W": [ "W_Wednesday_initial", null ],
380 | "F": [ "F_Friday_initial", null ],
381 | "Sun": [ "Sun", null ],
382 | "Mon": [ "Mon", null ],
383 | "Tue": [ "Tue", null ],
384 | "Wed": [ "Wed", null ],
385 | "Thu": [ "Thu", null ],
386 | "Fri": [ "Fri", null ],
387 | "Sat": [ "Sat", null ],
388 | "January": [ "January", null ],
389 | "February": [ "February", null ],
390 | "March": [ "March", null ],
391 | "April": [ "April", null ],
392 | "May": [ "May", null ],
393 | "June": [ "June", null ],
394 | "July": [ "July", null ],
395 | "August": [ "August", null ],
396 | "September": [ "September", null ],
397 | "October": [ "October", null ],
398 | "November": [ "November", null ],
399 | "December": [ "December", null ],
400 | "Jan": [ "Jan_January_abbreviation", null ],
401 | "Feb": [ "Feb_February_abbreviation", null ],
402 | "Apr": [ "Apr_April_abbreviation", null ],
403 | "Jun": [ "Jun_June_abbreviation", null ],
404 | "Jul": [ "Jul_July_abbreviation", null ],
405 | "Aug": [ "Aug_August_abbreviation", null ],
406 | "Sep": [ "Sep_September_abbreviation", null ],
407 | "Oct": [ "Oct_October_abbreviation", null ],
408 | "Nov": [ "Nov_November_abbreviation", null ],
409 | "Dec": [ "Dec_December_abbreviation", null ]
410 | },
411 | "placeholdersUsedOnPage": {
412 | "%1$s %2$s Feed": [ "%1$s %2$s Feed", "\u001f(.{0,100}?) (.{0,100}?) Feed\u001f", null ],
413 | "%1$s %2$s Comments Feed": [ "%1$s %2$s Comments Feed", "\u001f(.{0,100}?) (.{0,100}?) Comments Feed\u001f", null ],
414 | "%1$s %2$s %3$s Comments Feed": [ "%1$s %2$s %3$s Comments Feed", "\u001f(.{0,100}?) (.{0,100}?) (.{0,100}?) Comments Feed\u001f", null ],
415 | "%1$s %2$s %3$s Category Feed": [ "%1$s %2$s %3$s Category Feed", "\u001f(.{0,100}?) (.{0,100}?) (.{0,100}?) Category Feed\u001f", null ],
416 | "%1$s %2$s %3$s Tag Feed": [ "%1$s %2$s %3$s Tag Feed", "\u001f(.{0,100}?) (.{0,100}?) (.{0,100}?) Tag Feed\u001f", null ],
417 | "%1$s %2$s Posts by %3$s Feed": [ "%1$s %2$s Posts by %3$s Feed", "\u001f(.{0,100}?) (.{0,100}?) Posts by (.{0,100}?) Feed\u001f", null ],
418 | "%1$s %2$s Search Results for “%3$s” Feed": [ "%1$s %2$s Search Results for “%3$s” Feed", "\u001f(.{0,100}?) (.{0,100}?) Search Results for “(.{0,100}?)” Feed\u001f", null ],
419 | "%1$s %2$s %3$s Feed": [ "%1$s %2$s %3$s Feed", "\u001f(.{0,100}?) (.{0,100}?) (.{0,100}?) Feed\u001f", null ],
420 | "(by %1$s)": [ "(by %1$s)", "\u001f\\(by (.{0,100}?)\\)\u001f", "(by User Name)" ],
421 | "(by User Name)\\u0004(by %1$s)": [ "(by %1$s)", "\u001f\\(by (.{0,100}?)\\)\u001f", "(by User Name)" ],
422 | "%1$s on %2$s": [ "%1$s on %2$s", "\u001f(.{0,100}?) on (.{0,100}?)\u001f", "Blog Title on WordPress.com" ],
423 | "Blog Title on WordPress.com\\u0004%1$s on %2$s": [ "%1$s on %2$s", "\u001f(.{0,100}?) on (.{0,100}?)\u001f", "Blog Title on WordPress.com" ],
424 | "Continue reading %s": [ "Continue reading %s", "\u001fContinue reading (.{0,100}?)\u001f", null ],
425 | "\u001fContinue reading %s\u001f": [ "Continue reading %s", "\u001f\u001fContinue reading (.{0,100}?)\u001f\u001f", null ],
426 | "Watch: %s": [ "Watch: %s", "\u001fWatch\\: (.{0,100}?)\u001f", null ],
427 | "JavaScript required to play %s.": [ "JavaScript required to play %s.", "\u001fJavaScript required to play (.{0,100}?)\\.\u001f", null ],
428 | "Comment on %s": [ "Comment on %s", "\u001fComment on (.{0,100}?)\u001f", null ],
429 | "Proudly powered by %s": [ "Proudly powered by %s", "\u001fProudly powered by (.{0,100}?)\u001f", null ],
430 | "\u001fProudly powered by %s\u001f": [ "Proudly powered by %s", "\u001f\u001fProudly powered by (.{0,100}?)\u001f\u001f", null ],
431 | "View full size