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:
becarta
2025-05-23 15:10:00 +02:00
parent 8a3507dce0
commit 3168826fa8
581 changed files with 88691 additions and 494 deletions

172
node_modules/i18next-fs-backend/lib/index.js generated vendored Normal file
View 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