155 lines
4.1 KiB
TypeScript
155 lines
4.1 KiB
TypeScript
|
|
import { getCatalogs } from "@lingui/cli/api"
|
||
|
|
import type { CatalogType, MessageType } from "@lingui/conf"
|
||
|
|
import { getConfig } from "@lingui/conf"
|
||
|
|
|
||
|
|
import type { DevtoolMessage, UpdateMessageInput } from "../devtool/types"
|
||
|
|
import { getLinguiConfigFilename, getProjectRoot } from "./project.ts"
|
||
|
|
|
||
|
|
interface CatalogEntry {
|
||
|
|
catalog: Awaited<ReturnType<typeof getCatalogs>>[number]
|
||
|
|
key: string
|
||
|
|
}
|
||
|
|
|
||
|
|
async function getCatalogEntries(config: ReturnType<typeof getConfig>) {
|
||
|
|
const catalogs = await getCatalogs(config)
|
||
|
|
|
||
|
|
return catalogs.map<CatalogEntry>((catalog, index) => ({
|
||
|
|
catalog,
|
||
|
|
key: catalog.name ?? `catalog-${index + 1}`,
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
function toDevtoolMessage({
|
||
|
|
catalog,
|
||
|
|
id,
|
||
|
|
locale,
|
||
|
|
sourceEntry,
|
||
|
|
sourceLocale,
|
||
|
|
targetEntry,
|
||
|
|
}: {
|
||
|
|
catalog: string
|
||
|
|
id: string
|
||
|
|
locale: string
|
||
|
|
sourceEntry: MessageType | undefined
|
||
|
|
sourceLocale: string
|
||
|
|
targetEntry: MessageType | undefined
|
||
|
|
}): DevtoolMessage {
|
||
|
|
const source =
|
||
|
|
sourceEntry?.message ??
|
||
|
|
sourceEntry?.translation ??
|
||
|
|
(sourceLocale === locale ? targetEntry?.message : undefined) ??
|
||
|
|
id
|
||
|
|
const catalogTranslation = targetEntry?.translation ?? ""
|
||
|
|
const translation =
|
||
|
|
locale === sourceLocale && catalogTranslation.length === 0
|
||
|
|
? source
|
||
|
|
: catalogTranslation
|
||
|
|
|
||
|
|
return {
|
||
|
|
catalog,
|
||
|
|
comments: targetEntry?.comments ?? sourceEntry?.comments ?? [],
|
||
|
|
id,
|
||
|
|
missing: locale !== sourceLocale && translation.length === 0,
|
||
|
|
obsolete: Boolean(targetEntry?.obsolete ?? sourceEntry?.obsolete),
|
||
|
|
origins: (sourceEntry?.origin ?? targetEntry?.origin ?? []).map(
|
||
|
|
([file, line]) => ({ file, line })
|
||
|
|
),
|
||
|
|
source,
|
||
|
|
translation,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export class CatalogService {
|
||
|
|
readonly config: ReturnType<typeof getConfig>
|
||
|
|
readonly catalogs: Promise<readonly CatalogEntry[]>
|
||
|
|
|
||
|
|
constructor(projectFilename: string) {
|
||
|
|
this.config = getConfig({
|
||
|
|
configPath: getLinguiConfigFilename(projectFilename),
|
||
|
|
cwd: getProjectRoot(projectFilename),
|
||
|
|
})
|
||
|
|
this.catalogs = getCatalogEntries(this.config)
|
||
|
|
}
|
||
|
|
|
||
|
|
async getMessages(locale: string) {
|
||
|
|
this.assertLocale(locale)
|
||
|
|
|
||
|
|
const catalogs = await this.catalogs
|
||
|
|
const messages = await Promise.all(
|
||
|
|
catalogs.map(async ({ catalog, key }) => {
|
||
|
|
const [sourceCatalog = {}, targetCatalog = {}] = await Promise.all([
|
||
|
|
catalog.read(this.config.sourceLocale),
|
||
|
|
catalog.read(locale),
|
||
|
|
])
|
||
|
|
const ids = new Set([
|
||
|
|
...Object.keys(sourceCatalog),
|
||
|
|
...Object.keys(targetCatalog),
|
||
|
|
])
|
||
|
|
|
||
|
|
return Array.from(ids)
|
||
|
|
.sort((left, right) => left.localeCompare(right))
|
||
|
|
.map((id) =>
|
||
|
|
toDevtoolMessage({
|
||
|
|
catalog: key,
|
||
|
|
id,
|
||
|
|
locale,
|
||
|
|
sourceEntry: sourceCatalog[id],
|
||
|
|
sourceLocale: this.config.sourceLocale,
|
||
|
|
targetEntry: targetCatalog[id],
|
||
|
|
})
|
||
|
|
)
|
||
|
|
})
|
||
|
|
)
|
||
|
|
|
||
|
|
return messages.flat()
|
||
|
|
}
|
||
|
|
|
||
|
|
async updateMessage(input: UpdateMessageInput) {
|
||
|
|
this.assertLocale(input.locale)
|
||
|
|
|
||
|
|
const catalogs = await this.catalogs
|
||
|
|
const entry = catalogs.find((candidate) => candidate.key === input.catalog)
|
||
|
|
|
||
|
|
if (!entry) {
|
||
|
|
throw new Error(`Unknown catalog: ${input.catalog}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
const [sourceCatalog = {}, targetCatalog = {}] = await Promise.all([
|
||
|
|
entry.catalog.read(this.config.sourceLocale),
|
||
|
|
entry.catalog.read(input.locale),
|
||
|
|
])
|
||
|
|
const sourceEntry = sourceCatalog[input.id]
|
||
|
|
const currentEntry = targetCatalog[input.id]
|
||
|
|
|
||
|
|
if (!sourceEntry && !currentEntry) {
|
||
|
|
throw new Error(`Unknown message: ${input.id}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
const nextCatalog: CatalogType = {
|
||
|
|
...targetCatalog,
|
||
|
|
[input.id]: {
|
||
|
|
...sourceEntry,
|
||
|
|
...currentEntry,
|
||
|
|
translation: input.translation,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
await entry.catalog.write(input.locale, nextCatalog)
|
||
|
|
|
||
|
|
return toDevtoolMessage({
|
||
|
|
catalog: entry.key,
|
||
|
|
id: input.id,
|
||
|
|
locale: input.locale,
|
||
|
|
sourceEntry,
|
||
|
|
sourceLocale: this.config.sourceLocale,
|
||
|
|
targetEntry: nextCatalog[input.id],
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
private assertLocale(locale: string) {
|
||
|
|
if (!this.config.locales.includes(locale)) {
|
||
|
|
throw new Error(`Unknown locale: ${locale}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|