@@ -36,16 +36,46 @@ const DEFAULT_LOCALE_WINDOW_SIZE = 8;
3636 * SharedArrayBuffer is unavailable.
3737 *
3838 * @param translation The translation messages for a locale, if the locale has any.
39+ * @param translationIntegrity Optional content hash of the translation file for cache lookup.
40+ * @param translationCache Optional Cache instance for binary translation buffers.
3941 * @returns A SharedArrayBuffer or Blob containing the serialized messages, or undefined if none.
4042 */
41- function serializeTranslation (
43+ async function serializeTranslation (
4244 translation : Record < string , ɵParsedTranslation > | undefined ,
43- ) : SharedArrayBuffer | Blob | undefined {
45+ translationIntegrity ?: string ,
46+ translationCache ?: Cache < Uint8Array > ,
47+ ) : Promise < SharedArrayBuffer | Blob | undefined > {
4448 if ( ! translation ) {
4549 return undefined ;
4650 }
4751
4852 if ( typeof SharedArrayBuffer !== 'undefined' ) {
53+ if ( translationIntegrity && translationCache ) {
54+ // Look up or generate binary translation data in the persistent cache.
55+ // A Uint8Array view is stored in the cache store to allow binary persistence.
56+ const binaryData = await translationCache . getOrCreate ( translationIntegrity , ( ) => {
57+ return new Uint8Array ( encodeTranslationToBuffer ( translation ) ) ;
58+ } ) ;
59+
60+ // On a cache miss, getOrCreate returns the newly created Uint8Array backed by the
61+ // original SharedArrayBuffer. Return it directly to avoid an unnecessary allocation and copy.
62+ if (
63+ binaryData . buffer instanceof SharedArrayBuffer &&
64+ binaryData . byteOffset === 0 &&
65+ binaryData . byteLength === binaryData . buffer . byteLength
66+ ) {
67+ return binaryData . buffer ;
68+ }
69+
70+ // On a warm cache hit, the restored data is backed by a standard ArrayBuffer from disk.
71+ // Copy it into a SharedArrayBuffer so worker threads can access it via zero-copy shared memory.
72+ const buffer = new SharedArrayBuffer ( binaryData . byteLength ) ;
73+ new Uint8Array ( buffer ) . set ( binaryData ) ;
74+
75+ return buffer ;
76+ }
77+
78+ // When persistent caching is not configured, encode directly into a SharedArrayBuffer.
4979 return encodeTranslationToBuffer ( translation ) ;
5080 }
5181
@@ -145,7 +175,8 @@ export class I18nInliner {
145175 #cacheInitFailed = false ;
146176 #workerPool: WorkerPool ;
147177 #cacheStore: PersistentCacheStore | undefined ;
148- #cache: Cache < TransformedFileResult > | undefined ;
178+ #transformedFileCache: Cache < TransformedFileResult > | undefined ;
179+ #translationCache: Cache < Uint8Array > | undefined ;
149180 readonly #localizeFiles: ReadonlyMap < string , BuildOutputFile > ;
150181 readonly #unmodifiedFiles: Array < BuildOutputFile > ;
151182
@@ -239,6 +270,7 @@ export class I18nInliner {
239270
240271 const fileResultsByLocale = new Map < string , Map < string , TransformedFileResult > > ( ) ;
241272 for ( const { locale } of localeList ) {
273+ assert ( ! fileResultsByLocale . has ( locale ) , 'Duplicate locale provided to inliner: ' + locale ) ;
242274 fileResultsByLocale . set ( locale , new Map ( ) ) ;
243275 }
244276
@@ -256,23 +288,30 @@ export class I18nInliner {
256288 const localeCacheBases = new Map < string , string > ( ) ;
257289 const localeBlobs = new Map < string , Blob | SharedArrayBuffer | undefined > ( ) ;
258290
259- for ( const { locale, translation, translationIntegrity } of windowLocales ) {
260- localeBlobs . set ( locale , serializeTranslation ( translation ) ) ;
261-
262- if ( this . #cacheStore) {
263- localeCacheBases . set (
264- locale ,
265- calculateHash (
266- JSON . stringify ( {
267- locale,
268- translation : translationIntegrity || translation ,
269- missingTranslation,
270- localizeVersion,
271- } ) ,
272- ) ,
291+ await Promise . all (
292+ windowLocales . map ( async ( { locale, translation, translationIntegrity } ) => {
293+ const serialized = await serializeTranslation (
294+ translation ,
295+ translationIntegrity ,
296+ this . #translationCache,
273297 ) ;
274- }
275- }
298+ localeBlobs . set ( locale , serialized ) ;
299+
300+ if ( this . #cacheStore) {
301+ localeCacheBases . set (
302+ locale ,
303+ calculateHash (
304+ JSON . stringify ( {
305+ locale,
306+ translation : translationIntegrity || translation ,
307+ missingTranslation,
308+ localizeVersion,
309+ } ) ,
310+ ) ,
311+ ) ;
312+ }
313+ } ) ,
314+ ) ;
276315
277316 const cacheChecks : CacheCheckItem [ ] = [ ] ;
278317
@@ -284,7 +323,7 @@ export class I18nInliner {
284323 let cacheKey : string | undefined ;
285324 let cachedResultPromise : Promise < TransformedFileResult | null > = Promise . resolve ( null ) ;
286325
287- if ( this . #cache ) {
326+ if ( this . #transformedFileCache ) {
288327 const fileCacheKeyBase = localeCacheBases . get ( locale ) ;
289328 assert ( fileCacheKeyBase !== undefined , 'Cache base must exist for locale: ' + locale ) ;
290329
@@ -294,7 +333,7 @@ export class I18nInliner {
294333 hasher . update ( fileCacheKeyBase ) ;
295334 cacheKey = hasher . digest ( ) ;
296335
297- cachedResultPromise = this . #cache
336+ cachedResultPromise = this . #transformedFileCache
298337 . get ( cacheKey )
299338 . then ( ( val ) => val ?? null )
300339 . catch ( ( ) => null ) ;
@@ -458,8 +497,8 @@ export class I18nInliner {
458497 for ( const { locale, cacheKey } of batchEntries ) {
459498 fileResultsByLocale . get ( locale ) ?. set ( filename , unmodifiedResult ) ;
460499
461- if ( this . #cache && cacheKey ) {
462- cachePromises . push ( this . #cache . put ( cacheKey , unmodifiedResult ) ) ;
500+ if ( this . #transformedFileCache && cacheKey ) {
501+ cachePromises . push ( this . #transformedFileCache . put ( cacheKey , unmodifiedResult ) ) ;
463502 }
464503 }
465504 await Promise . allSettled ( cachePromises ) ;
@@ -469,9 +508,9 @@ export class I18nInliner {
469508 const matchingEntry = batchEntries . find ( ( e ) => e . locale === res . locale ) ;
470509 const cacheKey = matchingEntry ?. cacheKey ;
471510
472- if ( this . #cache && cacheKey ) {
511+ if ( this . #transformedFileCache && cacheKey ) {
473512 cachePromises . push (
474- this . #cache . put ( cacheKey , {
513+ this . #transformedFileCache . put ( cacheKey , {
475514 file : filename ,
476515 code : res . code ,
477516 map : res . map ,
@@ -519,6 +558,7 @@ export class I18nInliner {
519558 translation : Record < string , ɵParsedTranslation > | undefined ,
520559 templateCode : string ,
521560 templateId : string ,
561+ translationIntegrity ?: string ,
522562 ) : Promise < { code : string ; errors : string [ ] ; warnings : string [ ] } > {
523563 const hasLocalize = templateCode . includes ( LOCALIZE_KEYWORD ) ;
524564
@@ -535,7 +575,11 @@ export class I18nInliner {
535575 code : templateCode ,
536576 filename : templateId ,
537577 locale,
538- translation : serializeTranslation ( translation ) ,
578+ translation : await serializeTranslation (
579+ translation ,
580+ translationIntegrity ,
581+ this . #translationCache,
582+ ) ,
539583 } ,
540584 { name : 'inlineCode' } ,
541585 ) ;
@@ -589,7 +633,8 @@ export class I18nInliner {
589633 createPersistentCacheStore ( join ( persistentCachePath , 'angular-i18n' ) ) ,
590634 ] ) ;
591635 this . #cacheStore = cacheStore ;
592- this . #cache = cacheStore . createCache ( 'transforms' ) ;
636+ this . #transformedFileCache = cacheStore . createCache ( 'transforms' ) ;
637+ this . #translationCache = cacheStore . createCache ( 'translations' ) ;
593638 } catch {
594639 this . #cacheInitFailed = true ;
595640
0 commit comments