Files
simple-react-app-kit/packages/i18n/src/cli/catalog-service.ts
T

155 lines
4.1 KiB
TypeScript
Raw Normal View History

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}`)
}
}
}