Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions bot/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { IFiat } from '../util/fiatModel';
import { CommunityContext } from './modules/community/communityContext';
import { imageCache } from '../util/imageCache';
import { ImageProcessingError } from '../util/errors';
import { Community } from '../models';

const { I18n } = require('@grammyjs/i18n');

const startMessage = async (ctx: MainContext) => {
try {
Expand Down Expand Up @@ -738,11 +741,25 @@ const publishBuyOrderMessage = async (

const channel = await getOrderChannel(order);
if (channel === undefined) throw new Error('channel is undefined');

// Get the community language if available
let communityI18n = i18n;
if (order.community_id) {
const community = await Community.findOne({ _id: order.community_id });
if (community && community.language) {
communityI18n = new I18n({
defaultLanguageOnMissing: true,
locale: community.language,
directory: 'locales',
}).createContext(community.language);
}
}

// We send the message to the channel
const message1 = await bot.telegram.sendMessage(channel, publishMessage, {
reply_markup: {
inline_keyboard: [
[{ text: i18n.t('sell_sats'), callback_data: 'takebuy' }],
[{ text: communityI18n.t('sell_sats'), callback_data: 'takebuy' }],
],
},
});
Expand Down Expand Up @@ -776,11 +793,26 @@ const publishSellOrderMessage = async (
publishMessage += `:${order._id}:`;
const channel = await getOrderChannel(order);
if (channel === undefined) throw new Error('channel is undefined');

// Get the community language if available
let communityI18n = i18n;

if (order.community_id) {
const community = await Community.findOne({ _id: order.community_id });
if (community && community.language) {
communityI18n = new I18n({
defaultLanguageOnMissing: true,
locale: community.language,
directory: 'locales',
}).createContext(community.language);
}
}

// We send the message to the channel
const message1 = await ctx.telegram.sendMessage(channel, publishMessage, {
reply_markup: {
inline_keyboard: [
[{ text: i18n.t('buy_sats'), callback_data: 'takesell' }],
[{ text: communityI18n.t('buy_sats'), callback_data: 'takesell' }],
],
},
});
Expand Down
1 change: 1 addition & 0 deletions bot/middleware/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const stageMiddleware = () => {
CommunityModule.Scenes.updateSolversCommunityWizard,
CommunityModule.Scenes.updateFeeCommunityWizard,
CommunityModule.Scenes.updateDisputeChannelCommunityWizard,
CommunityModule.Scenes.updateLanguageCommunityWizard,
CommunityModule.Scenes.addEarningsInvoiceWizard,
addInvoicePHIWizard,
OrdersModule.Scenes.createOrder,
Expand Down
6 changes: 6 additions & 0 deletions bot/modules/community/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ export const updateCommunity = async (
user,
community,
});
} else if (field === 'language') {
ctx.scene.enter('UPDATE_LANGUAGE_COMMUNITY_WIZARD_SCENE_ID', {
id,
user,
community,
});
}
} catch (error) {
logger.error(error);
Expand Down
3 changes: 3 additions & 0 deletions bot/modules/community/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const configure = (bot: Telegraf<CommunityContext>) => {
await commands.updateCommunity(ctx, ctx.match[1], 'disputeChannel', bot);
},
);
bot.action(/^editLanguageBtn_([0-9a-f]{24})$/, userMiddleware, async ctx => {
await commands.updateCommunity(ctx, ctx.match[1], 'language');
});

bot.command('findcomms', userMiddleware, commands.findCommunity);
bot.action(
Expand Down
12 changes: 10 additions & 2 deletions bot/modules/community/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const createCommunityWizardStatus = (
try {
let { name, group } = state;
name = state.name || '__';
const language = state.language || '__';
let currencies = state.currencies && state.currencies.join(', ');
currencies = currencies || '__';
group = state.group || '__';
Expand All @@ -27,6 +28,7 @@ export const createCommunityWizardStatus = (
solvers = solvers || '__';
const text = [
i18n.t('name') + `: ${name}`,
i18n.t('language') + `: ${language}`,
i18n.t('currency') + `: ${currencies}`,
i18n.t('group') + `: ${group}`,
i18n.t('channels') + `: ${channels}`,
Expand Down Expand Up @@ -94,15 +96,21 @@ export const updateCommunityMessage = async (ctx: MainContext) => {
callback_data: `editDisputeChannelBtn_${id}`,
},
{
text: '💰 ' + ctx.i18n.t('earnings'),
callback_data: `earningsBtn_${id}`,
text: '✏️ ' + ctx.i18n.t('language'),
callback_data: `editLanguageBtn_${id}`,
},
],
[
{
text: '💰 ' + ctx.i18n.t('earnings'),
callback_data: `earningsBtn_${id}`,
},
{
text: visibilityText,
callback_data: `changeVisibilityBtn_${id}`,
},
],
[
{
text: '☠️ ' + ctx.i18n.t('delete_community'),
callback_data: `deleteCommunityAskBtn_${id}`,
Expand Down
24 changes: 24 additions & 0 deletions bot/modules/community/scenes.communityAdmin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Scenes } from 'telegraf';
import { CommunityContext } from './communityContext';
import { isValidLanguage } from '../../../util/languages';

import * as CommunityEvents from '../events/community';

Expand Down Expand Up @@ -37,6 +38,29 @@ const communityAdmin = () => {
}
});

scene.command('/setlanguage', async (ctx: CommunityContext) => {
try {
const [, maybeLanguage] = ctx.message!.text.split(' ');
if (!maybeLanguage || maybeLanguage.trim() === '') {
return ctx.reply(ctx.i18n.t('wizard_community_invalid_language'));
}

const language = maybeLanguage.trim().toLowerCase();
if (!isValidLanguage(language)) {
return ctx.reply(ctx.i18n.t('wizard_community_invalid_language'));
}

const { community } = ctx.scene.state as any;
community.language = language;
await community.save();
await ctx.reply(ctx.i18n.t('community_language_updated', { language }));
CommunityEvents.communityUpdated(community);
} catch (err) {
console.error('setlanguage error:', err);
return ctx.reply(ctx.i18n.t('generic_error'));
}
});

return scene;
};

Expand Down
79 changes: 79 additions & 0 deletions bot/modules/community/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './messages';
import { CommunityContext } from './communityContext';
import * as commAdmin from './scenes.communityAdmin';
import { isValidLanguage } from '../../../util/languages';

const CURRENCIES = parseInt(process.env.COMMUNITY_CURRENCIES || '10');

Expand All @@ -25,6 +26,7 @@ export const communityWizard = new Scenes.WizardScene<CommunityContext>(

const {
name,
language,
currencies,
group,
channels,
Expand Down Expand Up @@ -61,6 +63,7 @@ export const communityWizard = new Scenes.WizardScene<CommunityContext>(
}

if (undefined === name) return createCommunitySteps.name(ctx);
if (undefined === language) return createCommunitySteps.language(ctx);
if (undefined === currencies) return createCommunitySteps.currencies(ctx);
if (undefined === group) return createCommunitySteps.group(ctx);
if (undefined === channels) return createCommunitySteps.channels(ctx);
Expand All @@ -71,6 +74,7 @@ export const communityWizard = new Scenes.WizardScene<CommunityContext>(

const community = new Community({
name,
language,
currencies,
group,
order_channels: channels,
Expand Down Expand Up @@ -150,6 +154,38 @@ const createCommunitySteps = {

return ctx.wizard.next();
},
async language(ctx: CommunityContext) {
const prompt = await createCommunityPrompts.language(ctx);

ctx.wizard.state.handler = async (ctx: CommunityContext) => {
const text = ctx?.message?.text;
if (!text) {
await ctx.deleteMessage();
return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id);
}

ctx.wizard.state.error = null;
const lang = text.trim().toLowerCase();

if (!isValidLanguage(lang)) {
ctx.telegram.deleteMessage(ctx.chat!.id, ctx.message!.message_id);
ctx.wizard.state.error = ctx.i18n.t(
'wizard_community_invalid_language',
);
return await ctx.wizard.state.updateUI();
}

ctx.wizard.state.language = lang;
await ctx.wizard.state.updateUI();
await ctx.telegram.deleteMessage(
ctx.message!.chat.id,
ctx.message!.message_id,
);
return ctx.telegram.deleteMessage(prompt.chat.id, prompt.message_id);
};

return ctx.wizard.next();
},
async currencies(ctx: CommunityContext) {
const prompt = await createCommunityPrompts.currencies(ctx);

Expand Down Expand Up @@ -427,6 +463,9 @@ const createCommunityPrompts = {
async name(ctx: CommunityContext) {
return ctx.reply(ctx.i18n.t('wizard_community_enter_name'));
},
async language(ctx: CommunityContext) {
return ctx.reply(ctx.i18n.t('wizard_community_enter_language'));
},
async currencies(ctx: CommunityContext) {
return ctx.reply(ctx.i18n.t('wizard_community_enter_currency'));
},
Expand Down Expand Up @@ -837,6 +876,46 @@ export const updateDisputeChannelCommunityWizard = new Scenes.WizardScene(
},
);

export const updateLanguageCommunityWizard = new Scenes.WizardScene(
'UPDATE_LANGUAGE_COMMUNITY_WIZARD_SCENE_ID',
async (ctx: CommunityContext) => {
try {
const { community } = ctx.wizard.state;
let message =
ctx.i18n.t('language') + ': ' + (community.language || 'en') + '\n\n';
message += ctx.i18n.t('wizard_community_enter_language') + '\n\n';
message += ctx.i18n.t('wizard_to_exit');
await ctx.reply(message);

return ctx.wizard.next();
} catch (error) {
logger.error(error);
ctx.scene.leave();
}
},
async (ctx: CommunityContext) => {
try {
if (ctx.message === undefined) return ctx.scene.leave();

const lang = ctx.message.text.trim().toLowerCase();
if (!isValidLanguage(lang)) {
ctx.deleteMessage();
return await ctx.reply(ctx.i18n.t('wizard_community_invalid_language'));
}

const { community } = ctx.wizard.state;
community.language = lang;
await community.save();
await ctx.reply(ctx.i18n.t('operation_successful'));

return ctx.scene.leave();
} catch (error) {
logger.error(error);
ctx.scene.leave();
}
},
);
Comment on lines +879 to +917
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate locale files and check for the 'language' key

# First, find all locale files
echo "=== Finding locale files ==="
find . -type f \( -name "*.yaml" -o -name "*.yml" \) | grep -i locale | head -20

echo ""
echo "=== Checking for 'language' key in locale files ==="
# Check for language key in YAML files
for file in locales/*.yaml locales/*.yml 2>/dev/null; do
  if [ -f "$file" ]; then
    echo "File: $file"
    if grep -q "^language\s*:" "$file" 2>/dev/null; then
      echo "  ✓ 'language' key found"
    else
      echo "  ✗ 'language' key NOT found"
    fi
  fi
done

Repository: lnp2pBot/bot

Length of output: 480


🏁 Script executed:

#!/bin/bash
# Check for isValidLanguage function and examine the scenes file

echo "=== Locating isValidLanguage function ==="
rg "isValidLanguage" -A 5 -B 2

echo ""
echo "=== Examining updateLanguageCommunityWizard context ==="
# Check the file structure around the wizard
rg "updateLanguageCommunityWizard" -B 5 -A 40

Repository: lnp2pBot/bot

Length of output: 8734


🏁 Script executed:

#!/bin/bash
# Find i18n configuration
echo "=== Searching for i18n configuration ==="
rg "new TelegrafI18n" -A 5 -B 2

echo ""
echo "=== Checking if locales/en.yaml contains 'language' key ==="
head -100 locales/en.yaml | grep -n "language"

echo ""
echo "=== Counting lines and structure of en.yaml ==="
wc -l locales/en.yaml
head -30 locales/en.yaml

Repository: lnp2pBot/bot

Length of output: 2631


🏁 Script executed:

#!/bin/bash
# Search more thoroughly for i18n configuration
echo "=== Searching for i18n instantiation (various patterns) ==="
rg "i18n\s*=" -A 3 -B 2 | head -50

echo ""
echo "=== Searching for 'language' key in ALL locale files ==="
for locale in locales/*.yaml; do
  if grep -q "^language:" "$locale" 2>/dev/null; then
    echo "$(basename $locale): has 'language' key at root level"
  else
    # Try with spaces
    if grep -q "^\s*language:" "$locale" 2>/dev/null; then
      echo "$(basename $locale): has 'language' key (with indent)"
    else
      echo "$(basename $locale): NO 'language' key found"
    fi
  fi
done

echo ""
echo "=== Checking en.yaml for 'language' anywhere ==="
rg "language" locales/en.yaml

Repository: lnp2pBot/bot

Length of output: 1733


🏁 Script executed:

#!/bin/bash
# Confirm the exact structure of the 'language' key in en.yaml
echo "=== Exact context of 'language' key in en.yaml ==="
rg -A 2 -B 2 "^language:" locales/en.yaml

echo ""
echo "=== Checking all keys used in updateLanguageCommunityWizard ==="
echo "Looking for: 'language', 'wizard_community_enter_language', 'wizard_to_exit', 'operation_successful'"
echo ""
for key in "language" "wizard_community_enter_language" "wizard_to_exit" "operation_successful"; do
  echo "Key: $key"
  for locale in locales/*.yaml; do
    if rg -q "^[[:space:]]*$key:" "$locale" 2>/dev/null; then
      echo "$(basename $locale): exists"
    else
      echo "$(basename $locale): MISSING"
    fi
  done
  echo ""
done

Repository: lnp2pBot/bot

Length of output: 1481


Add 'language' key to non-English locale files to complete translations.

Line 885 uses ctx.i18n.t('language') to display the current language label. This key exists only in en.yaml and is missing from the 9 other locale files (de, es, fa, fr, it, ko, pt, ru, uk). Due to the configured fallback behavior (defaultLanguageOnMissing: true), non-English users will see the English label instead of a localized translation.

Add language: Language (or its appropriate translation) to each missing locale file to provide complete internationalization coverage.

🤖 Prompt for AI Agents
In bot/modules/community/scenes.ts around lines 879 to 917, the scene uses
ctx.i18n.t('language') but the 'language' key exists only in en.yaml; add the
missing key to each non-English locale file (de.yaml, es.yaml, fa.yaml, fr.yaml,
it.yaml, ko.yaml, pt.yaml, ru.yaml, uk.yaml) with an appropriate translation
(e.g., language: Language or the translated term) so non-English users see a
localized label; ensure correct YAML syntax, match surrounding key
style/indentation, and run a quick l10n smoke test to verify each locale returns
the new key.


export const addEarningsInvoiceWizard = new Scenes.WizardScene(
'ADD_EARNINGS_INVOICE_WIZARD_SCENE_ID',
async (ctx: CommunityContext) => {
Expand Down
17 changes: 16 additions & 1 deletion bot/ordersActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { UserDocument } from '../models/user';
import { HasTelegram, MainContext } from './start';
import { IOrder } from '../models/order';
import { IFiat } from '../util/fiatModel';

import * as OrderEvents from './modules/events/orders';

const { ObjectId } = require('mongoose').Types;
const { I18n } = require('@grammyjs/i18n');

interface CreateOrderArguments {
type: string;
Expand Down Expand Up @@ -73,10 +75,23 @@ const createOrder = async (
try {
amount = Math.floor(amount);
let isPublic = true;

// Use community language if community_id is provided
let descriptionI18n = i18n;

if (community_id) {
const community = await Community.findById(community_id);
if (community == null) throw new Error('community is null');
isPublic = community.public;

// Get community language for description
if (community.language) {
descriptionI18n = new I18n({
defaultLanguageOnMissing: true,
locale: community.language,
directory: 'locales',
}).createContext(community.language);
}
}
const fee = await getFee(amount, community_id || '');
if (process.env.MAX_FEE === undefined)
Expand Down Expand Up @@ -129,7 +144,7 @@ const createOrder = async (
tg_order_message: tgOrderMessage,
price_from_api: priceFromAPI,
price_margin: priceMargin || 0,
description: buildDescription(i18n, {
description: buildDescription(descriptionI18n, {
user,
type,
amount,
Expand Down
7 changes: 7 additions & 0 deletions locales/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@ community_admin: |
${community.fee}
Earnings:
${community.earnings}
Language:
${community.language || 'de'}
npub:
<code>${community.nostr_public_key || ''}</code>

Expand All @@ -580,7 +582,12 @@ community_admin_help: |
# commands

/setnpub &lt;npub&gt; - Configure Nostr community's public key.
/setlanguage &lt;lang&gt; - Konfiguriere die Sprache der Community für veröffentlichte Nachrichten.
community_npub_updated: You added the community's pubkey ${npub} successfully!
community_language_updated: Du hast die Sprache der Community erfolgreich auf ${language} aktualisiert!
wizard_community_enter_language: 'Gib den Sprachcode für deine Community ein (en, es, fr, de, it, pt, ru, uk, ko, fa)'
wizard_community_invalid_language: 'Ungültiger Sprachcode. Bitte gib einen der folgenden ein - en, es, fr, de, it, pt, ru, uk, ko, fa'
language: "Sprache"
# END modules/community

# START modules/orders
Expand Down
7 changes: 7 additions & 0 deletions locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ community_admin: |
${community.fee}
Earnings:
${community.earnings}
Language:
${community.language || 'en'}
npub:
<code>${community.nostr_public_key || ''}</code>

Expand All @@ -569,7 +571,12 @@ community_admin_help: |
# commands

/setnpub &lt;npub&gt; - Configure Nostr community's public key.
/setlanguage &lt;lang&gt; - Configure community's language for published messages.
community_npub_updated: You added the community's pubkey ${npub} successfully!
community_language_updated: You updated the community's language to ${language} successfully!
wizard_community_enter_language: 'Enter the language code for your community (en, es, fr, de, it, pt, ru, uk, ko, fa)'
wizard_community_invalid_language: 'Invalid language code. Please enter one of the following - en, es, fr, de, it, pt, ru, uk, ko, fa'
language: Language
# END modules/community

# START modules/nostr
Expand Down
Loading
Loading