Add internationalization support with astro-i18next integration
- Implemented astro-i18next for multi-language support, including English, Dutch, and Italian. - Configured default locale and language fallback settings. - Defined routes for localized content in the configuration. - Updated package.json and package-lock.json to include new dependencies for i18next and related plugins.
This commit is contained in:
172
node_modules/i18next-fs-backend/lib/index.js
generated
vendored
Normal file
172
node_modules/i18next-fs-backend/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import { defaults, debounce, getPath, setPath, pushPath } from './utils.js'
|
||||
import { readFile, readFileSync } from './readFile.js'
|
||||
import { writeFile, removeFile } from './writeFile.js'
|
||||
|
||||
const getDefaults = () => {
|
||||
return {
|
||||
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||
addPath: '/locales/{{lng}}/{{ns}}.missing.json',
|
||||
ident: 2,
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify
|
||||
// expirationTime: 60 * 60 * 1000
|
||||
}
|
||||
}
|
||||
|
||||
class Backend {
|
||||
constructor (services, options = {}, allOptions = {}) {
|
||||
this.services = services
|
||||
this.options = options
|
||||
this.allOptions = allOptions
|
||||
this.type = 'backend'
|
||||
this.init(services, options, allOptions)
|
||||
}
|
||||
|
||||
init (services, options = {}, allOptions = {}) {
|
||||
this.services = services
|
||||
this.options = defaults(options, this.options || {}, getDefaults())
|
||||
this.allOptions = allOptions
|
||||
this.queuedWrites = {}
|
||||
this.debouncedWrite = debounce(this.write, 250)
|
||||
}
|
||||
|
||||
read (language, namespace, callback) {
|
||||
let loadPath = this.options.loadPath
|
||||
if (typeof this.options.loadPath === 'function') {
|
||||
loadPath = this.options.loadPath(language, namespace)
|
||||
}
|
||||
const filename = this.services.interpolator.interpolate(loadPath, { lng: language, ns: namespace })
|
||||
if (this.allOptions.initAsync === false || this.allOptions.initImmediate === false) {
|
||||
try {
|
||||
const { data, stat } = readFileSync(filename, this.options)
|
||||
const timestamp = stat && stat.mtime && stat.mtime.getTime()
|
||||
if (this.options.expirationTime && timestamp && timestamp + this.options.expirationTime < Date.now()) {
|
||||
this.removeFile(language, namespace)
|
||||
return callback(new Error('File expired!'), false) // no retry
|
||||
}
|
||||
callback(null, data, timestamp)
|
||||
} catch (err) {
|
||||
callback(err, false) // no retry
|
||||
}
|
||||
return
|
||||
}
|
||||
readFile(filename, this.options)
|
||||
.then(({ data, stat }) => {
|
||||
const timestamp = stat && stat.mtime && stat.mtime.getTime()
|
||||
if (this.options.expirationTime && timestamp && timestamp + this.options.expirationTime < Date.now()) {
|
||||
this.removeFile(language, namespace)
|
||||
return callback(new Error('File expired!'), false) // no retry
|
||||
}
|
||||
callback(null, data, timestamp)
|
||||
})
|
||||
.catch((err) => callback(err, false)) // no retry
|
||||
}
|
||||
|
||||
create (languages, namespace, key, fallbackValue, callback) {
|
||||
if (typeof callback !== 'function') callback = () => {}
|
||||
if (typeof languages === 'string') languages = [languages]
|
||||
|
||||
let todo = languages.length
|
||||
const done = () => {
|
||||
if (!--todo) callback()
|
||||
}
|
||||
|
||||
languages.forEach((lng) => {
|
||||
// eslint-disable-next-line no-useless-call
|
||||
this.queue.call(this, lng, namespace, key, fallbackValue, done)
|
||||
})
|
||||
}
|
||||
|
||||
// this way i18next-fs-backend can be used as cache layer in combination with i18next-chained-backend
|
||||
save (language, namespace, data, callback) {
|
||||
if (!callback) callback = () => {}
|
||||
|
||||
const keys = Object.keys(data)
|
||||
let todo = keys.length
|
||||
const done = () => {
|
||||
if (!--todo) callback()
|
||||
}
|
||||
|
||||
keys.forEach((key) => {
|
||||
// eslint-disable-next-line no-useless-call
|
||||
this.queue.call(this, language, namespace, key, data[key], done)
|
||||
})
|
||||
}
|
||||
|
||||
removeFile (language, namespace) {
|
||||
let addPath = this.options.addPath
|
||||
if (typeof this.options.addPath === 'function') {
|
||||
addPath = this.options.addPath(language, namespace)
|
||||
}
|
||||
const filename = this.services.interpolator.interpolate(addPath, { lng: language, ns: namespace })
|
||||
removeFile(filename, this.options)
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
write () {
|
||||
for (const lng in this.queuedWrites) {
|
||||
const namespaces = this.queuedWrites[lng]
|
||||
if (lng !== 'locks') {
|
||||
for (const ns in namespaces) {
|
||||
this.writeFile(lng, ns)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeFile (lng, namespace) {
|
||||
const lock = getPath(this.queuedWrites, ['locks', lng, namespace])
|
||||
if (lock) return
|
||||
|
||||
let addPath = this.options.addPath
|
||||
if (typeof this.options.addPath === 'function') {
|
||||
addPath = this.options.addPath(lng, namespace)
|
||||
}
|
||||
|
||||
const filename = this.services.interpolator.interpolate(addPath, { lng, ns: namespace })
|
||||
|
||||
const missings = getPath(this.queuedWrites, [lng, namespace])
|
||||
setPath(this.queuedWrites, [lng, namespace], [])
|
||||
|
||||
if (missings.length) {
|
||||
// lock
|
||||
setPath(this.queuedWrites, ['locks', lng, namespace], true)
|
||||
|
||||
const proceed = ({ data }) => {
|
||||
missings.forEach((missing) => {
|
||||
const path = this.allOptions.keySeparator === false ? [missing.key] : (missing.key.split(this.allOptions.keySeparator || '.'))
|
||||
try {
|
||||
setPath(data, path, missing.fallbackValue)
|
||||
} catch (e) {
|
||||
if (path.length < 2 || !e.message || (e.message.indexOf('Cannot create property') < 0)) throw e
|
||||
setPath(data, [missing.key], missing.fallbackValue)
|
||||
}
|
||||
})
|
||||
|
||||
const proceedWrite = () => {
|
||||
// unlock
|
||||
setPath(this.queuedWrites, ['locks', lng, namespace], false)
|
||||
missings.forEach((missing) => {
|
||||
if (missing.callback) missing.callback()
|
||||
})
|
||||
// rerun
|
||||
this.debouncedWrite()
|
||||
}
|
||||
writeFile(filename, data, this.options)
|
||||
.then(proceedWrite)
|
||||
.catch(proceedWrite)
|
||||
}
|
||||
readFile(filename, this.options).then(proceed).catch(() => proceed({ data: {} }))
|
||||
}
|
||||
}
|
||||
|
||||
queue (lng, namespace, key, fallbackValue, callback) {
|
||||
pushPath(this.queuedWrites, [lng, namespace], { key, fallbackValue: fallbackValue || '', callback })
|
||||
this.debouncedWrite()
|
||||
}
|
||||
}
|
||||
|
||||
Backend.type = 'backend'
|
||||
|
||||
export default Backend
|
Reference in New Issue
Block a user