Skip to content

Commit

Permalink
Markdown
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Jan 21, 2025
1 parent 895a707 commit f24aadf
Show file tree
Hide file tree
Showing 29 changed files with 311 additions and 34 deletions.
11 changes: 8 additions & 3 deletions apps/web/src/db/queries/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,17 @@ export const updateOrganizationApiKey = async (organizationId: string) => {
.get();
};

export const getOrganizationTotalKeys = async (organizationId: string) => {
return db
.select({ total: count(translations.translationKey) })
export const getOrganizationLimits = async (organizationId: string) => {
const t = await db
.select({ totalKeys: count(translations.translationKey) })
.from(translations)
.where(eq(translations.organizationId, organizationId))
.get();

return {
keysCount: t?.totalKeys ?? 0,
documentsCount: 0,
};
};

export const updateOrganizationTier = async (
Expand Down
46 changes: 46 additions & 0 deletions apps/web/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,49 @@ export const translations = sqliteTable(
index("translations_project_id_idx").on(table.projectId),
],
);

export const documents = sqliteTable(
"documents",
{
id: text()
.primaryKey()
.$defaultFn(() => createId()),
projectId: text("project_id")
.notNull()
.references(() => projects.id, { onDelete: "cascade" }),
organizationId: text("organization_id")
.notNull()
.references(() => organizations.id, { onDelete: "cascade" }),
userId: text("user_id").references(() => users.id, { onDelete: "cascade" }),
sourceFormat: text("source_format").notNull(),
sourceLanguage: text("source_language").notNull(),
targetLanguage: text("target_language").notNull(),
sourceText: text("source_text").notNull(),
translatedText: text("translated_text").notNull(),
documentName: text("document_name").notNull(),
branch: text("branch"),
commit: text("commit"),
commitLink: text("commit_link"),
sourceProvider: text("source_provider"),
commitMessage: text("commit_message"),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
},
(table) => [
index("project_documents_idx").on(table.projectId),
index("documents_created_at_idx").on(table.createdAt),
uniqueIndex("unique_document_idx").on(
table.projectId,
table.targetLanguage,
table.documentName,
),
index("org_documents_idx").on(table.organizationId),
index("documents_source_language_idx").on(table.sourceLanguage),
index("documents_target_language_idx").on(table.targetLanguage),
index("documents_project_id_idx").on(table.projectId),
],
);
30 changes: 28 additions & 2 deletions apps/web/src/jobs/translate/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createTranslation } from "@/db/queries/translate";
import { metadata, schemaTask } from "@trigger.dev/sdk/v3";
import { z } from "zod";
import { calculateChunkSize } from "../utils/chunk";
import { translate } from "../utils/translate";
import { translateDocument, translateKeys } from "../utils/translate";

const translationSchema = z.object({
projectId: z.string(),
Expand Down Expand Up @@ -46,6 +46,32 @@ export const translateTask = schemaTask({
Array<{ key: string; translatedText: string }>
> = {};

// If the source format is markdown, we take the whole document and translate it
if (payload.sourceFormat === "md") {
for (const targetLocale of payload.targetLanguages) {
const translatedContent = await translateDocument(
payload.content.at(0)?.sourceText ?? "",
{
sourceLocale: payload.sourceLanguage,
targetLocale,
},
);

translations[targetLocale] = [
{
key: "content",
translatedText: translatedContent,
},
];

// createDocument
}

return {
translations,
};
}

const totalTranslations =
payload.targetLanguages.length * payload.content.length;

Expand Down Expand Up @@ -76,7 +102,7 @@ export const translateTask = schemaTask({
Math.round((completedTranslations * 100) / totalTranslations),
);

const translatedContent = await translate(
const translatedContent = await translateKeys(
chunk,
{
sourceLocale: payload.sourceLanguage,
Expand Down
11 changes: 10 additions & 1 deletion apps/web/src/jobs/utils/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ Translation Requirements:
- Respect existing whitespace and newline patterns
`;

const mapFormatToPrompt = (format?: string) => {
switch (format) {
case "md":
return "Markdown";
default:
return "JSON";
}
};

export function createFinalPrompt(
text: string,
options: PromptOptions,
settings?: Partial<typeof projectSettings.$inferSelect>,
) {
const basePrompt = `You are a professional translator working with JSON files.
const basePrompt = `You are a professional translator working with ${mapFormatToPrompt(options.sourceFormat)} files.
Task: Translate the content below from ${options.sourceLocale} to ${options.targetLocale}.
${baseRequirements}`;
Expand Down
22 changes: 20 additions & 2 deletions apps/web/src/jobs/utils/translate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generateObject } from "ai";
import { z } from "zod";
import { chooseModel } from "./model";
import { chooseModel, getModels } from "./model";
import { createFinalPrompt } from "./prompt";
import type { PromptOptions } from "./types";

Expand All @@ -19,7 +19,7 @@ ${sourceText}
return createFinalPrompt(codeblocks, options);
}

export async function translate(
export async function translateKeys(
content: Array<{ key: string; sourceText: string }>,
options: PromptOptions,
totalItems: number,
Expand All @@ -36,3 +36,21 @@ export async function translate(

return object.content;
}

export async function translateDocument(
content: string,
options: PromptOptions,
) {
const { large } = getModels();
const prompt = createFinalPrompt(content, options);

const { object } = await generateObject({
model: large,
prompt,
schema: z.object({
content: z.string(),
}),
});

return object.content;
}
1 change: 1 addition & 0 deletions apps/web/src/jobs/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import type { projectSettings } from "@/db/schema";
export type PromptOptions = {
sourceLocale: string;
targetLocale: string;
sourceFormat?: string;
settings?: Partial<typeof projectSettings.$inferSelect>;
};
12 changes: 12 additions & 0 deletions apps/web/src/lib/tiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ export const TIERS_MAX_KEYS = {
7: 500000,
8: 1000000,
};

export const TIERS_MAX_DOCUMENTS = {
0: 5,
1: 20,
2: 50,
3: 100,
4: 200,
5: 500,
6: 1000,
7: 2000,
8: 5000,
};
9 changes: 5 additions & 4 deletions apps/web/src/trpc/routers/jobs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { db } from "@/db";
import { getOrganizationTotalKeys } from "@/db/queries/organization";
import { getOrganizationLimits } from "@/db/queries/organization";
import { organizations, projects } from "@/db/schema";
import type { translateTask } from "@/jobs/translate/translate";
import { TIERS_MAX_KEYS } from "@/lib/tiers";
Expand Down Expand Up @@ -52,12 +52,13 @@ export const jobsRouter = createTRPCRouter({

const isFreeUser = org?.plan === "free";

const totalKeys = await getOrganizationTotalKeys(org?.id);
const { keysCount, documentsCount } = await getOrganizationLimits(
org?.id,
);

// Calculate the total number of keys, saved keys + new keys (for each target language)
const nextTotalKeys =
(totalKeys?.total ?? 0) +
input.content.length * input.targetLanguages.length;
keysCount + input.content.length * input.targetLanguages.length;

const currentKeysLimit =
TIERS_MAX_KEYS[org.tier as keyof typeof TIERS_MAX_KEYS];
Expand Down
40 changes: 40 additions & 0 deletions examples/namespace/docs/de/welcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Willkommen bei Languine

Dies ist eine Willkommensnachricht.

## Erste Schritte

Um mit unserer Anwendung zu beginnen:

1. Installieren Sie die Abhängigkeiten mit `npm install`
2. Konfigurieren Sie Ihre Umgebungsvariablen in `.env`
3. Starten Sie den Entwicklungsserver mit `npm run dev`

## Funktionen

Unsere Anwendung umfasst:

- Echtzeit-Datensynchronisierung
- Mehrsprachige Unterstützung
- Responsives Design
- Dunkle/Helle Themenmodi

## Dokumentation

Weitere detaillierte Informationen finden Sie unter:

- [Installationsanleitung](./installation.md)
- [API-Referenz](./api-reference.md)
- [Richtlinien zur Mitwirkung](./contributing.md)

## Unterstützung

Brauchen Sie Hilfe? Sie können:

- Unserer [Discord-Community](https://discord.gg/example) beitreten
- Ein Problem auf [GitHub](https://github.com/example/repo) öffnen
- Uns eine E-Mail an [email protected] senden

## Lizenz

Dieses Projekt steht unter der MIT-Lizenz - siehe die [LICENSE](./LICENSE) Datei für Details.
2 changes: 1 addition & 1 deletion examples/namespace/docs/en/welcome.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Welcome
## Welcome to Languine

This is a welcome message.

Expand Down
40 changes: 40 additions & 0 deletions examples/namespace/docs/es/welcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Bienvenido a Languine

Este es un mensaje de bienvenida.

## Comenzando

Para comenzar con nuestra aplicación:

1. Instala las dependencias ejecutando `npm install`
2. Configura tus variables de entorno en `.env`
3. Inicia el servidor de desarrollo con `npm run dev`

## Características

Nuestra aplicación incluye:

- Sincronización de datos en tiempo real
- Soporte para múltiples idiomas
- Diseño responsivo
- Modos de tema oscuro/claro

## Documentación

Para obtener información más detallada, consulta:

- [Guía de Instalación](./installation.md)
- [Referencia de la API](./api-reference.md)
- [Pautas de Contribución](./contributing.md)

## Soporte

¿Necesitas ayuda? Puedes:

- Unirte a nuestra [comunidad de Discord](https://discord.gg/example)
- Abrir un problema en [GitHub](https://github.com/example/repo)
- Enviarnos un correo electrónico a [email protected]

## Licencia

Este proyecto está bajo la licencia MIT - consulta el archivo [LICENCIA](./LICENSE) para más detalles.
40 changes: 40 additions & 0 deletions examples/namespace/docs/fr/welcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Bienvenue sur Languine

Ceci est un message de bienvenue.

## Pour commencer

Pour commencer avec notre application :

1. Installez les dépendances en exécutant `npm install`
2. Configurez vos variables d'environnement dans `.env`
3. Démarrez le serveur de développement avec `npm run dev`

## Fonctionnalités

Notre application comprend :

- Synchronisation des données en temps réel
- Support multilingue
- Conception réactive
- Modes de thème sombre/clair

## Documentation

Pour plus d'informations détaillées, consultez :

- [Guide d'installation](./installation.md)
- [Référence API](./api-reference.md)
- [Lignes directrices pour contribuer](./contributing.md)

## Support

Besoin d'aide ? Vous pouvez :

- Rejoindre notre [communauté Discord](https://discord.gg/example)
- Ouvrir un problème sur [GitHub](https://github.com/example/repo)
- Nous envoyer un e-mail à [email protected]

## Licence

Ce projet est sous licence MIT - voir le fichier [LICENCE](./LICENSE) pour plus de détails.
Loading

0 comments on commit f24aadf

Please sign in to comment.