diff --git a/locales/en.ftl b/locales/en.ftl new file mode 100644 index 0000000..b862980 --- /dev/null +++ b/locales/en.ftl @@ -0,0 +1,3 @@ +invalid_url = Invalid url +invalid_download_urls = download urls not found +generic = Something went wrong \ No newline at end of file diff --git a/locales/ru.ftl b/locales/ru.ftl new file mode 100644 index 0000000..f429f51 --- /dev/null +++ b/locales/ru.ftl @@ -0,0 +1,3 @@ +invalid_url = Неверная ссылка +invalid_download_urls = Не найдены ссылки для скачивания +generic = Что-то пошло не так \ No newline at end of file diff --git a/package.json b/package.json index 5938d66..4d1aa40 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "license": "ISC", "packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531", "dependencies": { + "@grammyjs/i18n": "^1.1.2", "@tobyg74/tiktok-api-dl": "^1.3.4", "@types/node": "^24.2.1", "grammy": "^1.37.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51b9350..6f9e5ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@grammyjs/i18n': + specifier: ^1.1.2 + version: 1.1.2(grammy@1.37.0) '@tobyg74/tiktok-api-dl': specifier: ^1.3.4 version: 1.3.4(@types/node@24.2.1)(canvas@3.1.2)(typescript@5.9.2) @@ -164,6 +167,12 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@deno/shim-deno-test@0.5.0': + resolution: {integrity: sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==} + + '@deno/shim-deno@0.18.2': + resolution: {integrity: sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA==} + '@emnapi/core@1.4.5': resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} @@ -386,6 +395,20 @@ packages: '@fastify/busboy@3.1.1': resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==} + '@fluent/bundle@0.17.1': + resolution: {integrity: sha512-CRFNT9QcSFAeFDneTF59eyv3JXFGhIIN4boUO2y22YmsuuKLyDk+N1I/NQUYz9Ab63e6V7T6vItoZIG/2oOOuw==} + engines: {node: '>=12.0.0', npm: '>=7.0.0'} + + '@fluent/langneg@0.6.2': + resolution: {integrity: sha512-YF4gZ4sLYRQfctpUR2uhb5UyPUYY5n/bi3OaED/Q4awKjPjlaF8tInO3uja7pnLQcmLTURkZL7L9zxv2Z5NDwg==} + engines: {node: '>=12.0.0', npm: '>=7.0.0'} + + '@grammyjs/i18n@1.1.2': + resolution: {integrity: sha512-PcK06mxuDDZjxdZ5HywBhr+erEITsR816KP4DNIDDds1jpA45pfz/nS9FdZmzF8H6lMyPix3mV5WL1rT4q+BuA==} + engines: {node: '>=12'} + peerDependencies: + grammy: ^1.10.0 + '@grammyjs/types@3.21.0': resolution: {integrity: sha512-IMj0EpmglPCICuyfGRx4ENKPSuzS2xMSoPgSPzHC6FtnWKDEmJLBP/GbPv/h3TAeb27txqxm/BUld+gbJk6ccQ==} @@ -2288,6 +2311,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + isomorphic-ws@5.0.0: resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} peerDependencies: @@ -3405,6 +3432,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3608,6 +3640,13 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@deno/shim-deno-test@0.5.0': {} + + '@deno/shim-deno@0.18.2': + dependencies: + '@deno/shim-deno-test': 0.5.0 + which: 4.0.0 + '@emnapi/core@1.4.5': dependencies: '@emnapi/wasi-threads': 1.0.4 @@ -3773,6 +3812,17 @@ snapshots: '@fastify/busboy@3.1.1': {} + '@fluent/bundle@0.17.1': {} + + '@fluent/langneg@0.6.2': {} + + '@grammyjs/i18n@1.1.2(grammy@1.37.0)': + dependencies: + '@deno/shim-deno': 0.18.2 + '@fluent/bundle': 0.17.1 + '@fluent/langneg': 0.6.2 + grammy: 1.37.0 + '@grammyjs/types@3.21.0': {} '@graphql-eslint/eslint-plugin@4.4.0(@types/node@24.2.1)(eslint@9.33.0(jiti@2.5.1))(graphql@16.11.0)(typescript@5.9.2)': @@ -6053,6 +6103,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + isomorphic-ws@5.0.0(ws@8.18.3): dependencies: ws: 8.18.3 @@ -7307,6 +7359,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: diff --git a/src/constants/messages.ts b/src/constants/messages.ts deleted file mode 100644 index 47fff1c..0000000 --- a/src/constants/messages.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const ERROR_MESSAGES = { - INVALID_URL: 'Invalid url', - INVALID_DOWNLOAD_URLS: 'Invalid download urls found', - GENERIC: 'Something went wrong', -}; diff --git a/src/index.ts b/src/index.ts index 6be3a4a..ee41ea5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,30 @@ import { env } from './config/env'; import { Downloader } from '@tobyg74/tiktok-api-dl'; -import { Bot, InputFile } from 'grammy'; +import { Bot, Context, InputFile } from 'grammy'; import { logger } from './utils/logger'; -import { ERROR_MESSAGES } from './constants/messages'; import { validateTikTokUrl } from './utils/urls'; +import { I18n, I18nFlavor } from '@grammyjs/i18n'; -const bot = new Bot(env.BOT_TOKEN, { +type MyContext = Context & I18nFlavor; + +const bot = new Bot(env.BOT_TOKEN, { client: { apiRoot: env.TELEGRAM_API_ROOT, }, }); +const i18n = new I18n({ + defaultLocale: 'en', // see below for more information + directory: 'locales', // Load all translation files from locales/. +}); + +bot.use(i18n); + bot.on('message:text', async (ctx) => { try { const url = ctx.message.text; - if (!validateTikTokUrl(url)) return ctx.reply(ERROR_MESSAGES.INVALID_URL); + if (!validateTikTokUrl(url)) return ctx.reply(ctx.t('invalid_url')); const { result, message } = await Downloader(url, { version: 'v3' }); @@ -25,7 +34,7 @@ bot.on('message:text', async (ctx) => { const imagesUrls = result?.images; if (!videoUrl && !imagesUrls?.length) { - return ctx.reply(ERROR_MESSAGES.INVALID_DOWNLOAD_URLS); + return ctx.reply(ctx.t('invalid_download_urls')); } if (result?.type === 'video' && videoUrl) { @@ -38,7 +47,7 @@ bot.on('message:text', async (ctx) => { } catch (error) { logger.error(error); - return ctx.reply(ERROR_MESSAGES.GENERIC); + return ctx.reply(ctx.t('generic')); } });