diff --git a/apps/api/package.json b/apps/api/package.json index a87b767..286f54e 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -18,6 +18,7 @@ "@repo/config": "*", "@repo/logger": "*", "@repo/typescript-config": "*", + "@repo/db": "*", "dotenv": "^17.1.0", "hono": "^4.8.4", "hono-openapi": "^0.4.8", diff --git a/apps/bot/locales/en.ftl b/apps/bot/locales/en.ftl new file mode 100644 index 0000000..406ecc9 --- /dev/null +++ b/apps/bot/locales/en.ftl @@ -0,0 +1 @@ +start = Hello, how can I help you \ No newline at end of file diff --git a/apps/bot/locales/ru.ftl b/apps/bot/locales/ru.ftl new file mode 100644 index 0000000..4f62ecb --- /dev/null +++ b/apps/bot/locales/ru.ftl @@ -0,0 +1 @@ +start = Привет, как я могу тебе помочь \ No newline at end of file diff --git a/apps/bot/package.json b/apps/bot/package.json index 2bb7191..c15e804 100644 --- a/apps/bot/package.json +++ b/apps/bot/package.json @@ -11,11 +11,12 @@ "clean": "rm -rf dist" }, "dependencies": { + "@grammyjs/i18n": "^1.1.2", "@hono/node-server": "^1.15.0", "@repo/config": "*", "@repo/db": "*", - "@repo/typescript-config": "*", "@repo/logger": "*", + "@repo/typescript-config": "*", "dotenv": "^17.1.0", "grammy": "^1.37.0", "hono": "^4.8.4", diff --git a/apps/bot/src/index.ts b/apps/bot/src/index.ts index efebf13..00da0c6 100644 --- a/apps/bot/src/index.ts +++ b/apps/bot/src/index.ts @@ -7,6 +7,7 @@ import { PrismaStorageAdapter } from './session-storage'; import { BotContext } from './types'; import { upsertUser } from './middlewares/upsert-user'; import { onError } from './middlewares/on-error'; +import { I18n } from '@grammyjs/i18n'; const bot = new Bot(config.BOT_TOKEN); const storage = new PrismaStorageAdapter(); @@ -15,9 +16,20 @@ bot.use(onError); bot.use(session({ storage, initial: () => ({}) })); bot.use(upsertUser); +const i18n = new I18n({ + defaultLocale: 'ru', + useSession: true, // whether to store user language in session + directory: 'locales', // Load all translation files from locales/. + localeNegotiator: async (ctx) => { + const session = await ctx.session; + return session.__language_code ?? ctx.from?.language_code ?? 'ru'; + }, +}); + +bot.use(i18n); + bot.command('start', async (ctx) => { - const session = await ctx.session; - await ctx.reply('Привет, ' + JSON.stringify(session)); + await ctx.reply(ctx.t('start')); }); if (config.MODE === 'webhook') { diff --git a/apps/bot/src/session-storage.ts b/apps/bot/src/session-storage.ts index a5772bd..b115cad 100644 --- a/apps/bot/src/session-storage.ts +++ b/apps/bot/src/session-storage.ts @@ -1,5 +1,5 @@ -import { prisma } from "@repo/db"; -import { StorageAdapter } from "grammy"; +import { prisma } from '@repo/db'; +import { StorageAdapter } from 'grammy'; export class PrismaStorageAdapter implements StorageAdapter @@ -14,7 +14,6 @@ export class PrismaStorageAdapter .then((session) => (session?.data as T) || undefined); } async write(key: string, data: T): Promise { - console.log("write", data); await prisma.telegramSession.upsert({ where: { id: key, diff --git a/apps/bot/src/types.ts b/apps/bot/src/types.ts index af4ef51..0ba6b01 100644 --- a/apps/bot/src/types.ts +++ b/apps/bot/src/types.ts @@ -1,5 +1,6 @@ -import { Context, LazySessionFlavor } from "grammy"; +import { I18nFlavor } from '@grammyjs/i18n'; +import { Context, LazySessionFlavor } from 'grammy'; // config your session data -export type SessionData = {}; -export type BotContext = Context & LazySessionFlavor; +export type SessionData = { __language_code?: string }; +export type BotContext = Context & LazySessionFlavor & I18nFlavor; diff --git a/package-lock.json b/package-lock.json index 2393f53..96aafc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,6 +149,7 @@ "apps/bot": { "version": "0.0.0", "dependencies": { + "@grammyjs/i18n": "^1.1.2", "@hono/node-server": "^1.15.0", "@repo/config": "*", "@repo/db": "*", @@ -224,6 +225,46 @@ "zod": "^3.20.2" } }, + "node_modules/@deno/shim-deno": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@deno/shim-deno/-/shim-deno-0.18.2.tgz", + "integrity": "sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA==", + "license": "MIT", + "dependencies": { + "@deno/shim-deno-test": "^0.5.0", + "which": "^4.0.0" + } + }, + "node_modules/@deno/shim-deno-test": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", + "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", + "license": "MIT" + }, + "node_modules/@deno/shim-deno/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@deno/shim-deno/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", @@ -833,6 +874,43 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fluent/bundle": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@fluent/bundle/-/bundle-0.17.1.tgz", + "integrity": "sha512-CRFNT9QcSFAeFDneTF59eyv3JXFGhIIN4boUO2y22YmsuuKLyDk+N1I/NQUYz9Ab63e6V7T6vItoZIG/2oOOuw==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@fluent/langneg": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@fluent/langneg/-/langneg-0.6.2.tgz", + "integrity": "sha512-YF4gZ4sLYRQfctpUR2uhb5UyPUYY5n/bi3OaED/Q4awKjPjlaF8tInO3uja7pnLQcmLTURkZL7L9zxv2Z5NDwg==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@grammyjs/i18n": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@grammyjs/i18n/-/i18n-1.1.2.tgz", + "integrity": "sha512-PcK06mxuDDZjxdZ5HywBhr+erEITsR816KP4DNIDDds1jpA45pfz/nS9FdZmzF8H6lMyPix3mV5WL1rT4q+BuA==", + "license": "MIT", + "dependencies": { + "@deno/shim-deno": "~0.18.0", + "@fluent/bundle": "^0.17.1", + "@fluent/langneg": "^0.6.2" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "grammy": "^1.10.0" + } + }, "node_modules/@grammyjs/types": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.21.0.tgz", @@ -2523,12 +2601,6 @@ "node": ">=0.8" } }, - "node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", - "license": "MIT" - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4375,12 +4447,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6545,25 +6611,6 @@ "zod": "^3.21.4" } }, - "node_modules/zod-prisma-types": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.2.4.tgz", - "integrity": "sha512-S4spVBMJAmecLv+aLyRhXK26qW9nWwcsOf1H1fRcEmiI8DPbftZ99u0fhqHlymuTpmMcUpuKxcNNOIqNY0ScSQ==", - "license": "MIT", - "dependencies": { - "@prisma/generator-helper": "^6.3.0", - "code-block-writer": "^12.0.0", - "lodash": "^4.17.21", - "zod": "^3.24.1" - }, - "bin": { - "zod-prisma-types": "dist/bin.js" - }, - "peerDependencies": { - "@prisma/client": "^4.x.x || ^5.x.x || ^6.x.x", - "prisma": "^4.x.x || ^5.x.x || ^6.x.x" - } - }, "packages/config": { "name": "@repo/config", "version": "0.0.0", @@ -6583,8 +6630,7 @@ "dependencies": { "@prisma/client": "^6.11.1", "@repo/exceptions": "*", - "prisma-json-types-generator": "^3.5.1", - "zod-prisma-types": "^3.2.4" + "prisma-json-types-generator": "^3.5.1" }, "devDependencies": { "@repo/eslint-config": "*",