11import * as zip from "@zip.js/zip.js" ;
22import prettyBytes from "pretty-bytes" ;
3- import { sum } from "../utilities/iterators" ;
43import { createFileSystem } from "./FileSystem" ;
5- import { FSFolder } from "./FileSystem" ;
4+ import type { FSFolder } from "./FileSystem" ;
5+ import Translations from "./modules/Translations" ;
66import type { ExtensionSummary } from "./types/ExtensionSummary" ;
77import type { Manifest } from "./types/Manifest" ;
8- import type { Translations } from "./types/Translations" ;
98
109export default class Extension {
1110 readonly manifest : Readonly < Manifest > ;
1211 readonly files : FSFolder ;
13- readonly # translations: Map < string , Translations > ;
12+ readonly translations : Translations ;
1413
15- private constructor ( root : FSFolder , manifest : Manifest , translations : Map < string , Translations > ) {
14+ private constructor ( root : FSFolder , manifest : Manifest , translations : Translations ) {
1615 /* Some of the initialization happens in the static create method because the constructor is not async. */
1716
1817 this . files = root ;
1918 this . manifest = Object . freeze ( manifest ) ;
20- this . # translations = translations ;
19+ this . translations = translations ;
2120 }
2221
2322 /**
@@ -35,57 +34,11 @@ export default class Extension {
3534 const manifest = JSON . parse ( rawManifest . replace ( / ^ \/ \/ .+ $ / gm, "" ) ) as Manifest ;
3635
3736 // Translations
38- const translations = new Map < string , Translations > ( ) ;
39- if ( manifest . default_locale !== undefined ) {
40- // default_locale must be present if the _locales subdirectory is present, must be absent otherwise.
41- // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/default_locale
42-
43- await Promise . all (
44- Array . from ( files . getFolder ( "_locales" ) ! . children . values ( ) )
45- . filter ( ( node ) => node instanceof FSFolder )
46- . filter ( ( folder ) => folder . getFile ( "messages.json" , false ) )
47- . map ( async ( folder ) => {
48- const rawFileContent = await folder . getFile ( "messages.json" ) . text ( ) ;
49- translations . set ( folder . name , JSON . parse ( rawFileContent ) ) ;
50- } )
51- ) ;
52-
53- if ( ! translations . has ( manifest . default_locale ) ) {
54- console . warn ( `Default locale (${ manifest . default_locale } ) missing.` ) ;
55- }
56- }
37+ const translations = await Translations . create ( manifest , files ) ;
5738
5839 return new Extension ( files , manifest , translations ) ;
5940 }
6041
61- i18n (
62- messageName : string ,
63- options : Partial < { substitutions : string | string [ ] ; locale : string } > = { }
64- ) : string {
65- const { substitutions = [ ] , locale = this . manifest . default_locale } = options ;
66-
67- if ( ! locale ) {
68- return messageName ;
69- }
70-
71- const translations = this . #translations. get ( locale ) ;
72- if ( ! translations ) {
73- return messageName ;
74- }
75-
76- const translation = translations [ messageName ] ;
77-
78- if ( translation !== undefined ) {
79- if ( substitutions . length > 0 ) {
80- // FIXME
81- console . warn ( "Substitutions currently not supported." ) ;
82- }
83- return translation . message ;
84- }
85-
86- return messageName ;
87- }
88-
8942 /**
9043 * Get a blob: URL for the given file.
9144 * If timeout is a positive number the URL will be revoked automatically.
@@ -120,44 +73,13 @@ export default class Extension {
12073 } ;
12174 }
12275
123- /**
124- * Get the translated messages for the given locale and some meta information including:
125- * - messages (keys) that are missing (not translated) in this locale
126- * - the percentage of translated messages in this locale
127- */
128- getTranslations ( locale : string ) : TranslationsInfo | undefined {
129- let actualLocale = locale ;
130- let messages = this . #translations. get ( actualLocale ) ;
131-
132- if ( ! messages && locale . includes ( "-" ) ) {
133- // If the requested locale is en-US we can use en as a fallback.
134- actualLocale = locale . split ( "-" ) [ 0 ] ;
135- messages = this . #translations. get ( actualLocale ) ;
136- }
137-
138- if ( ! messages ) {
139- return undefined ;
140- }
141-
142- const allMessageKeys = new Set ( this . #translations. values ( ) . flatMap ( ( t ) => Object . keys ( t ) ) ) ;
143- const translatedKeys = new Set ( Object . keys ( messages ) ) ;
144- const missingKeys = allMessageKeys . difference ( translatedKeys ) ;
145-
146- return {
147- percentage : translatedKeys . size / allMessageKeys . size ,
148- missingKeys : missingKeys ,
149- messages : messages ,
150- locale : actualLocale
151- } ;
152- }
153-
15476 getLocales ( ) {
155- return [ ... this . # translations. keys ( ) ] ;
77+ return this . translations . getLocales ( ) ;
15678 }
15779
15880 get meta ( ) : Omit < ExtensionSummary [ "meta" ] , "icon" > {
15981 return {
160- name : this . #__MSG_i18n ( this . manifest . name ) ,
82+ name : this . translations . i18nForManifestKey ( this . manifest . name ) ,
16183 version : this . manifest . version ,
16284 source : "file" , // FIXME
16385 author : this . #getAuthor( ) ,
@@ -166,47 +88,6 @@ export default class Extension {
16688 } ;
16789 }
16890
169- get translationInfo ( ) : ExtensionSummary [ "translations" ] {
170- const messageKeys = new Set ( this . #translations. values ( ) . flatMap ( ( t ) => Object . keys ( t ) ) ) . size ;
171- const translatedMessages = sum ( this . #translations. values ( ) . map ( ( t ) => Object . keys ( t ) . length ) ) ;
172-
173- return {
174- locales : this . getLocales ( ) ,
175- messages : messageKeys ,
176- defaultLocale : this . manifest . default_locale ,
177- percentage :
178- this . #translations. size * messageKeys > 0
179- ? translatedMessages / ( this . #translations. size * messageKeys )
180- : undefined
181- } ;
182- }
183-
184- /**
185- * Get translations of localized manifest strings.
186- * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization#internationalizing_manifest.json
187- */
188- #__MSG_i18n( rawString : string , locale = this . manifest . default_locale ) : string {
189- if ( ! locale || ! this . #translations. has ( locale ) ) {
190- return rawString ;
191- }
192-
193- const matches = rawString . match ( / ^ _ _ M S G _ ( .+ ) _ _ $ / ) ;
194-
195- if ( matches === null || matches . length !== 2 ) {
196- return rawString ;
197- }
198-
199- const messageName = matches [ 1 ] ;
200-
201- const translation = this . #translations. get ( locale ) ! [ messageName ] ;
202-
203- if ( translation !== undefined ) {
204- return translation . message ;
205- }
206-
207- return rawString ;
208- }
209-
21091 #getAuthor( ) : string | undefined {
21192 const author = this . manifest . author ;
21293
@@ -242,10 +123,3 @@ export type PermissionsInfo = {
242123 optional : string [ ] ;
243124 } ;
244125} ;
245-
246- export type TranslationsInfo = {
247- percentage : number ;
248- missingKeys : Set < string > ;
249- messages : Translations ;
250- locale : string ;
251- } ;
0 commit comments