UNPKG

next-intl

Version:

Internationalization (i18n) for Next.js

2 lines (1 loc) 7.24 kB
import e from"fs/promises";import s from"path";import{resolveCodec as t,getFormatExtension as a}from"../format/index.js";import o from"../source/SourceFileScanner.js";import r from"../source/SourceFileWatcher.js";import{getDefaultProjectRoot as i,localeCompare as c,compareReferences as n}from"../utils.js";import l from"./CatalogLocales.js";import h from"./CatalogPersister.js";import g from"./SaveScheduler.js";class d{static extractorOwnedAggregatorKeys=new Set(["description","id","message","references"]);sourceMessagesByFile=new Map;sourceMessagesById=new Map;messagesById=new Map;translationsByTargetLocale=new Map;lastWriteByLocale=new Map;constructor(e,s){this.config=e,this.saveScheduler=new g(s.saveDebounceMs??50),this.projectRoot=s.projectRoot??i(),this.isDevelopment=s.isDevelopment??!1,this.extractor=s.extractor,this.isDevelopment&&(this.sourceWatcher=new r(this.getSrcPaths(),this.handleFileEvents.bind(this)),this.sourceWatcher.start())}async getCodec(){return this.codec||(this.codec=await t(this.config.messages.format,this.projectRoot)),this.codec}async getPersister(){return this.persister||(this.persister=new h({messagesPath:this.config.extract.path,codec:await this.getCodec(),extension:a(this.config.messages.format)})),this.persister}getCatalogLocales(){if(this.catalogLocales)return this.catalogLocales;{const e=s.join(this.projectRoot,this.config.extract.path);return this.catalogLocales=new l({messagesDir:e,extension:a(this.config.messages.format),locales:this.config.extract.locales,sourceLocale:this.config.extract.sourceLocale}),this.catalogLocales}}async getTargetLocales(){return this.getCatalogLocales().getTargetLocales()}getSrcPaths(){return(Array.isArray(this.config.extract.srcPath)?this.config.extract.srcPath:[this.config.extract.srcPath]).map((e=>s.join(this.projectRoot,e)))}async loadMessages(){const e=await this.loadSourceMessages();if(this.loadCatalogsPromise=this.loadTargetMessages(),await this.loadCatalogsPromise,this.scanCompletePromise=(async()=>{const s=Array.from(await o.getSourceFiles(this.getSrcPaths())).toSorted(c),t=await Promise.all(s.map((async e=>({filePath:e,messages:await this.extractFile(e)}))));for(const{filePath:e,messages:s}of t)s&&this.applyFileMessages(e,s);this.mergeSourceDiskMetadata(e)})(),await this.scanCompletePromise,this.isDevelopment){this.getCatalogLocales().subscribeLocalesChange(this.onLocalesChange)}}async loadSourceMessages(){const e=await this.loadLocaleMessages(this.config.extract.sourceLocale),s=new Map;for(const t of e)s.set(t.id,t);return s}async loadLocaleMessages(e){const s=await this.getPersister(),t=await s.read(e),a=await s.getLastModified(e);return this.lastWriteByLocale.set(e,a),t}async loadTargetMessages(){const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.reloadLocaleCatalog(e))))}async reloadLocaleCatalog(e){const s=await this.loadLocaleMessages(e);if(e===this.config.extract.sourceLocale)for(const e of s){const s=this.messagesById.get(e.id);if(s)for(const t of Object.keys(e))d.extractorOwnedAggregatorKeys.has(t)||(s[t]=e[t])}else{const t=this.translationsByTargetLocale.get(e),a=t&&t.size>0;if(s.length>0){const t=new Map;for(const e of s)t.set(e.id,e);this.translationsByTargetLocale.set(e,t)}else if(a);else{const s=new Map;this.translationsByTargetLocale.set(e,s)}}}mergeSourceDiskMetadata(e){for(const[s,t]of e){const e=this.messagesById.get(s);if(e)for(const s of Object.keys(t))d.extractorOwnedAggregatorKeys.has(s)||null!=e[s]||(e[s]=t[s])}}async processFile(e){const s=await this.extractFile(e);return!!s&&this.applyFileMessages(e,s)}async extractFile(s){let t=[];try{const a=await e.readFile(s,"utf8");let o;try{o=await this.extractor.extract(s,a)}catch{return}t=o.messages}catch(e){if("ENOENT"!==e.code)throw e}return t}applyFileMessages(e,s){const t=this.sourceMessagesByFile.get(e),a=this.groupSourceMessagesById(s),o=new Set([...t?.keys()??[],...a.keys()]);a.size>0?this.sourceMessagesByFile.set(e,a):this.sourceMessagesByFile.delete(e);for(const s of o){const t=this.sourceMessagesById.get(s);t&&(t.delete(e),0===t.size&&this.sourceMessagesById.delete(s));const o=a.get(s);if(o){let t=this.sourceMessagesById.get(s);t||(t=new Map,this.sourceMessagesById.set(s,t)),t.set(e,o)}this.rebuildMessageById(s)}return this.haveMessagesChangedForFile(t,a)}groupSourceMessagesById(e){const s=new Map;for(const t of e){const e=s.get(t.id);e?e.push(t):s.set(t.id,[t])}return s}rebuildMessageById(e){const s=Array.from(this.sourceMessagesById.get(e)?.values()??[]).flat();if(0===s.length)return void this.messagesById.delete(e);const t=this.messagesById.get(e),a={description:this.mergeDescriptions(s),id:e,message:s[0].message,references:s.map((e=>e.reference)).sort(n)};if(t)for(const e of Object.keys(t))d.extractorOwnedAggregatorKeys.has(e)||null!=a[e]||(a[e]=t[e]);this.messagesById.set(e,a)}mergeDescriptions(e){const s=e.toSorted(((e,s)=>n(e.reference,s.reference))),t=[];for(const e of s){const{description:s}=e;null==s||t.includes(s)||t.push(s)}return t}haveMessagesChangedForFile(e,s){if(!e)return s.size>0;if(e.size!==s.size)return!0;for(const[t,a]of e){const e=s.get(t);if(!e)return!0;if(!this.areSourceMessageArraysEqual(a,e))return!0}return!1}areSourceMessageArraysEqual(e,s){return e.length===s.length&&e.every(((e,t)=>this.areSourceMessagesEqual(e,s[t])))}areSourceMessagesEqual(e,s){return e.id===s.id&&e.message===s.message&&e.description===s.description&&e.reference.path===s.reference.path&&e.reference.line===s.reference.line}async save(){return this.saveScheduler.schedule((()=>this.saveImpl()))}async saveImpl(){await this.saveLocale(this.config.extract.sourceLocale);const e=await this.getTargetLocales();await Promise.all(e.map((e=>this.saveLocale(e))))}async saveLocale(e){await this.loadCatalogsPromise;const s=Array.from(this.messagesById.values()),t=await this.getPersister(),a=e===this.config.extract.sourceLocale,o=this.lastWriteByLocale.get(e),r=await t.getLastModified(e);r&&o&&r>o&&await this.reloadLocaleCatalog(e);const i=a?this.messagesById:this.translationsByTargetLocale.get(e),c=s.map((e=>{const s=i?.get(e.id);return{...s,id:e.id,description:e.description,references:e.references,message:a?e.message:s?.message??""}}));await t.write(c,{locale:e,sourceMessagesById:this.messagesById});const n=await t.getLastModified(e);this.lastWriteByLocale.set(e,n)}onLocalesChange=async e=>{this.loadCatalogsPromise=Promise.all([this.loadCatalogsPromise,...e.added.map((e=>this.reloadLocaleCatalog(e)))]);for(const s of e.added)await this.saveLocale(s);for(const s of e.removed)this.translationsByTargetLocale.delete(s),this.lastWriteByLocale.delete(s)};async handleFileEvents(e){this.loadCatalogsPromise&&await this.loadCatalogsPromise,this.scanCompletePromise&&await this.scanCompletePromise;let s=!1;const t=await this.sourceWatcher.expandDirectoryDeleteEvents(e,Array.from(this.sourceMessagesByFile.keys()));for(const e of t.toSorted(((e,s)=>c(e.path,s.path)))){const t=await this.processFile(e.path);s||=t}s&&await this.save()}[Symbol.dispose](){this.sourceWatcher?.stop(),this.sourceWatcher=void 0,this.saveScheduler[Symbol.dispose](),this.catalogLocales&&this.isDevelopment&&this.catalogLocales.unsubscribeLocalesChange(this.onLocalesChange)}}export{d as default};