Skip to content

Storage Interface

The storage interface provides a way to implement a custom storage handler for catalogs. The handler is provided to the adapter config as storage.

An item is a unit (can be a message or a URL) extracted from the source code or the URL patterns you configured, with full translation and reference details.

type FileRefEntry = {
link?: string | undefined // for URLs
placeholders: [number, string][]
}
type FileRef = {
file: string
/**
* multiple references in the same file
* null when there is no link or placeholders just that it's referenced
*/
refs: (FileRefEntry | null)[]
}
interface Item {
context?: string | undefined
translations: Map<string, string[]>
references: FileRef[]
urlAdapters: string[] // for URLs
// for things that should survive the round trip with the storage
[key: string]: unknown
}

And this is the unit that forms the basis for what exchanged with wuchale.

An additional thing that has to be stored with the translations is the plural rules.

type PluralRule = {
nplurals: number
plural: string
}
type PluralRules = Map<string, PluralRule>

There are just two exchange interactions with wuchale: saving data and loading data. And what the storage has to exchange is this:

type SaveData = {
pluralRules: PluralRules // will always be provided
items: Item[]
}
type LoadData = {
pluralRules?: PluralRules | undefined // optional if it's the first time etc, will be filled by the default one
items: Item[]
}

Since almost all of the data exchanged is text files, wuchale provides a file system abstraction layer that reads and handles non existing file errors, returning null, and such conveniences. However, the reason for its existence is swappability for testing and for the npx wuchale check --full command which goes through the normal process of extraction but doesn't write anything because it uses a read-only instance of the file system layer. To make a new storage interface work like that, you can use this layer, provided by wuchale. It looks like this:

type FS = {
read(file: string): string | null | Promise<string | null>
write(file: string, content: string): void | Promise<void>
mkdir(path: string): void | Promise<void>
exists(path: string): boolean | Promise<boolean>
unlink(path: string): boolean | Promise<boolean>
}

Putting it all together, what wuchale expects as a storage is a StorageFactory function that accepts StorageFactoryOpts options from wuchale, and returns a CatalogStorage object.

type StorageFactoryOpts = {
locales: string[]
root: string
/** shared locale artifacts directory from the top-level config */
localesDir: string
/** whether the url is configured, can use to load separate url files */
haveUrl: boolean
sourceLocale: string
fs: FS
}
type CatalogStorage = {
/**
* the key to check if two storages share the same location
* e.g. this can be the dir for the pofile storage
* two storages with same keys means they are the same/shared
*/
key: string
load(): LoadData | Promise<LoadData>
save(data: SaveData): void | Promise<void>
/** the files controlled by this storage, for e.g. for Vite to watch */
files: string[]
}
type StorageFactory = (opts: StorageFactoryOpts) => CatalogStorage | Promise<CatalogStorage>