Evgenii Legotckoi
Evgenii Legotckoi30 травня 2016 р. 08:13

Qt/C++ - Урок 049. QTranslator - динамічний переклад багатомовного додатка на Qt

Рано чи пізно розробник програм на Qt зустрічається з необхідністю підтримки мультимовності у своєму додатку. Ось тоді на допомогу і приходить клас QTranslator та спеціалізований додаток для створення перекладів Qt Linguist .

Об'єкт класу QTranslator використовується для завантаження перекладів із спеціального файлу з розширенням .qm, який є шістнадцятковим файлом перекладів. Цей файл компілюється з файлу перекладів у форматі XML, який має розширення ts і прописується в pro файлі проекту. Цей файл містить усі рядки програми, які були укладені у функцію tr(). Рекомендую задавати весь інтерфейс програми англійською мовою, який буде мовою за замовчуванням, а переклади вже з потрібною мовою підвантажуватиметься з файлів перекладів. Якщо потрібний файл перекладу не буде знайдено, автоматично буде завантажено переклад англійською мовою. Хоча Ви, звичайно, можете застосувати й іншу мову як стандартну мову.


Приклад завантаження перекладу

Структура імені файлу перекладу відіграє в роботі з QTranslator. Розберемо мінімальний приклад.

#include "mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QLibraryInfo>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QTranslator qtLanguageTranslator;
    qtLanguageTranslator.load(QString("QtLanguage_") + QString("ru_RU"));
    qApp->installTranslator(&qtLanguageTranslator);

    MainWindow w;
    w.show();

    return a.exec();
}

У даному прикладі створюється об'єкт класу QTranslator, в який завантажується цікавий для нас файл із зазначенням мови. У даному випадку повна назва файлу перекладів наступна QtLanguage_ru.qm. Тобто дано спільну для файлу перекладів назву QtLanguage, а також префікс мови перекладу _ru . Але ви вже помітили, що як аргумент для методу load формується рядок QtLanguage_ru_RU. Це робиться для того, щоб визначити потрібний діалект мови, наприклад американську (en_US) або британську (en_GB) англійську. Але якщо в назві файлу перекладу немає вказівки діалекту, буде вибраний файл без уточнення діалекту, тобто QtLanguage_ua.qm.

Після того, як файл перекладу завантажено, його необхідно встановити у програму. У цьому випадку використано глобальний покажчик на програму qApp->installTranslator(&qtLanguageTranslator).

Створення файлу перекладу

Після того, як ми розібралися з мінімальним способом встановлення перекладу в додаток, давайте розберемося з тим, як взагалі можна створити переклад. Для створення перекладу необхідно користуватися функціями tr(), trUtf8(), translate() у додатку тощо. Тобто весь текст, який вимагатиме перекладу, необхідно обрамляти саме в ці функції, щоб потім створити файл перекладу. Виглядати це може так:

label->setText(QApplication::translate("MainWindow", "Select Language", 0));
label_2->setText(tr("Hello"));
label_3->setText(trUtf8("Hello world"));

Після того, як у додатку помічені всі необхідні для перекладу рядки, необхідно буде створити файл перекладів, зробити безпосередньо сам переклад та скомпілювати підсумковий файл. Для цього використовуються такі програми:

  1. lupdate - програма для формування файлу перекладу з поля TRANSLATIONS у pro файлі, а також оновлення інформації про всі нові рядки у додатку, які потребують перекладу.
  2. lrelease - програма для збирання підсумкового файлу перекладів, який використовуватиметься в додатку.
  3. Qt Linguist – безпосередньо сам пакет для створення перекладів.

Першим кроком, який потрібно буде зробити, після того, як у додатку вже прописані рядки, що вимагають перекладу, це додати файл для перекладу в pro-файл проекту. Можливо, Ви також захочете вказати інформацію про кодування, яке застосовується для перекладу.

TRANSLATIONS += QtLanguage_ru.ts

CODECFORSRC     = UTF-8

Далі необхідно скористатися утилітою lupdate для створення файлу QtLanguage_ru.ts. У Qt Creator шукайте тут: Інструменти -> Зовнішні -> Linguist -> lupdate .

lupdate повідомить про результат пошуку рядків для перекладу.

Запускается внешняя утилита «D:\Qt\5.6\mingw49_32\bin\lupdate.exe» D:/AndroidQT/QTProjects/QtLanguage/QtLanguage.pro
Updating 'QtLanguage_ru.ts'...
    Found 4 source text(s) (0 new and 4 already existing)

«D:\Qt\5.6\mingw49_32\bin\lupdate.exe» завершилась

Далі відкриваємо Qt Linguist робимо переклад всіх рядків, позначаючи, які з них перекладені (Це чисто службова інформація для самого перекладача, щоб не згадувати, що було перекладено, а що ні.)

Після створення перекладу збережіть файл і скомпілюйте шістнадцятковий файл перекладу, скориставшись утилітою *lrelease. Її можна знайти в Qt Creator там же, де і lupdate, або запустити з Qt Linguist: File -> Release.

Для застосування файлу необхідно буде помістити його в каталог з файлом програми, що виконується, а в коді прописати шлях до цього файлу.

Динамічний переклад програми

Завантаження перекладу програми - це добре, але що якщо потрібно змінювати переклад динамічно? Давайте розглянемо невеликий приклад, у якому є QComboBox із зазначенням двох мов: англійської та російської, за зміною якого відбуватиметься зміна перекладу в додатку.

Структура проекта

  • QtLanguage.pro - профайл проекту;
  • main.cpp - файл проекту з функцією main
  • mainwindow.h - заголовний файл головного вікна програми;
  • mainwindow.cpp - файл вихідних кодів головного вікна програми;
  • mainwindow.ui - файл форми головного вікна програми.

mainwindow.ui

Я віддаю перевагу використати графічний дизайнер для створення графічного інтерфейсу програми, оскільки це прискорює розробку, якщо не потрібно чогось складного у зовнішньому вигляді програми, тому створимо вікно з наступним зовнішнім виглядом.

У цьому вікні є:

  • етикетка
  • етикетка_2
  • поле зі списком

З перекладом яких ми і працюватимемо. Зручність графічного дизайнера полягає також і в тому, що він автоматично формує метод retranslateUi(), який використовується для зміни перекладу всіх підписів, що використовуються в додатку, хоча можна і самим прописати вручну подібний метод, але особисто я не хочу втрачати час на те, що чудово може бути створено автоматично. Наприклад, у даному уроці він виглядає так:

void retranslateUi(QMainWindow *MainWindow)
{
    MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", 0));
    label_2->setText(QApplication::translate("MainWindow", "The QTranslator class provides internationalization support for text output.An object of this class contains a set of translations from a source language to a target language. QTranslator provides functions to look up translations in a translation file. Translation files are created using Qt Linguist.", 0));
    label->setText(QApplication::translate("MainWindow", "Select Language", 0));
} // retranslateUi

QtLanguage.pro

Як говорилося вище, вказуємо ім'я файлу перекладу у профайлі проекту.

#-------------------------------------------------
#
# Project created by QtCreator 2016-05-22T14:34:42
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = QtLanguage
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp

HEADERS  += mainwindow.h

FORMS    += mainwindow.ui

TRANSLATIONS = QtLanguage_ru.ts

CODECFORSRC     = UTF-8

main.cpp

А ось у цьому файлі нічого не мінятимемо.

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MainWindow w;
    w.show();

    return a.exec();
}

mainwindow.h

У заголовному файлі головного вікна програми необхідно оголосити об'єкта класу QTranslator та перевизначити метод changeEvent(QEvent *event) , у якому визначатиметься подія зміни мови програми.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTranslator>
#include <QEvent>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    // Метод получения событий в главном окне приложения
    // В нём будет производиться проверка события смены перевода приложения
    void changeEvent(QEvent * event) override;

private:
    Ui::MainWindow *ui;
    QTranslator qtLanguageTranslator;   // Выделяем перевод в отдельном поле, иначе ничего работать не будет
};

#endif // MAINWINDOW_H

mainwindow.cpp

Логіка роботи програми буде наступною: при зміні пункту в комбобоксі змінюватиметься переклад програми.

Примітка. Не забудьте покласти файл перекладу з розширенням qm поряд з виконуваним файлом програми та зібрати необхідні dll, інакше не запрацює.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>
#include <QTranslator>
#include <QLibraryInfo>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // Задаём два пункта с текстом локалей в комбобоксе
    ui->comboBox->addItems(QStringList() << "ru_RU" << "en_US");

    // подключаем к сигналу изменения пункта комбобокса лямбда функцию,
    // в которой будет изменяться перевод приложения
    // Здесь имеется интересный момент. Поскольку QComboBox имеет перегрузку сигнатуры сигнала,
    // то нам необходимо скастовать сигнал к нужной сигнатуре.
    // В данном случае будем использовать название пункта при его изменении
    connect(ui->comboBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
            [=](const QString &str){
        qtLanguageTranslator.load("QtLanguage_" + str, ".");   // Загружаем перевод
        qApp->installTranslator(&qtLanguageTranslator);        // Устанавливаем перевод в приложение
    });

    // Сделаем первоначальную инициализацию перевода для окна прилоежния
    qtLanguageTranslator.load(QString("QtLanguage_") + QString("ru_RU"));
    qApp->installTranslator(&qtLanguageTranslator);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::changeEvent(QEvent *event)
{
    // В случае получения события изменения языка приложения
    if (event->type() == QEvent::LanguageChange) {
        ui->retranslateUi(this);    // переведём окно заново
    }
}

Підсумок

В результаті Ви отримаєте програму з підтримкою двох мов у своєму графічному інтерфейсі.

Завантажити приклад програми з QTranslator

Відеоурок

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

w
  • 16 травня 2017 р. 00:45

Не работает....

Evgenii Legotckoi
  • 16 травня 2017 р. 00:51

Очень содержательный комментарий. По моему опыту, не работает всегда потому, что кто-то что-то не правильно делает. А в видео показано, что все отлично работает.

АК
  • 24 серпня 2017 р. 09:56

А к чему относится такая проблема? Есть QPlainTextEdit. У него есть горячие клавиши. Но они работает только на английском языке : Когда на клавиатуре нажимается ctrl + 'x', горячая клавиша "вырезать" срабатывает. Но при нажатии ctrl + 'ч' не срабатывает. Стоит создать тему на форуме или это что-то само собой разумеющиеся?
Данный код в main.cpp не помог :

    QString translatorFileName = QLatin1String("qt_");
    translatorFileName += QLocale::system().name();
    QTranslator *translator = new QTranslator(&a);
    if (translator->load(translatorFileName, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
        a.installTranslator(translator);

А вы случаем не переводили эти сочетания клавиш там, где они устанавливаются? Если где-то есть этот перевод, то есть там подставлена tr() b trUtf8() функция, то могут быть проблемы, лучше те куски кода не оборачивать в функции перевода и не делать перевода, тем более, что это и не нужно. Скорее всего коды клавиш неправильно определяются.

IscanderChe
  • 12 липня 2018 р. 06:08
Скачал пример, увы, не работает. Комбобокс исправно пашет, переключения языков нет.

Ещё подскажите, как без шаманства кастования переключать язык? Например, как это обычно бывает, после перезагрузки приложения.
Evgenii Legotckoi
  • 12 липня 2018 р. 06:11

Странно. Должен был бы работать... проверю на досуге.

Вообще сохраняют инфоромацию о языке в QSettings, который нужно установить. Закрывают приложение и после запуска из QSettings подтягивается инофрмация о локализации приложения, после чего загружается требуемый язык.
Evgenii Legotckoi
  • 12 липня 2018 р. 06:17

Если использовать QComboBox для выбора языка, то с новым синтаксисом сигналов и слотов без каста не получится, но можно использовать шаблонный функционал Qt для упрощения работы с сигналами и слотам. Подробнее в этой статье про QOverload

Arrow
  • 13 липня 2018 р. 07:55
Хорошая статья. Только один вопрос как это сделать для CMake?

Интересует именно запись в CMakeList

  1. TRANSLATIONS += QtLanguage_ru.ts
  2. CODECFORSRC = UTF-8
Пытался так, но не работает и хотя в документации написано так.

file(GLOB TRANSLATION_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.ts)

qt5_add_translation(TRANSLATION_QM ${TRANSLATION_FILES})
add_custom_target(translations DEPENDS ${TRANSLATION_QM})
Ругается на
qt5_add_translation
пишет - неизвестная команда.

qt5_create_translation понимает, но ничего не происходит.
Evgenii Legotckoi
  • 14 липня 2018 р. 14:49

У меня на руках есть один проект, где какие-то потуги с переводами и подключением этого добра в CMAKE делались.

Но там файл перевода добавляется прямо в ресурсы проекта. То есть бинарных qm файл переводов добавлялся в qrc файл. То есть при компилировании перевод сохранялся как ресурс, а потом уже из ресурсов забирался.
 
qt5_add_resources(RCC_RESOURCES
        resources/translations.qrc
        )

add_executable({RCC_RESOURCES})
Как понимаю,
qt5_add_translation или qt5_create_translation
Должны ещё создать этот самый бинарный qm файл переводов
Arrow
  • 14 липня 2018 р. 14:58

Спасибо,  попробую.

Arrow
  • 17 липня 2018 р. 09:34
Работает так:

find_package (Qt5LinguistTools)
file (GLOB TS_FILES ${SOURCE_DIR}/translations/*.ts)
qt5_add_translation (QM_FILES ${TS_FILES})
add_custom_target (translations ALL DEPENDS ${QM_FILES})
......
add_executable (${PROJECT_NAME} ${SOURCE_FILES} ${HEADER_FILES} ${UI_FILES} ${RESOURCES} ${QM_FILES})
grig_p
  • 13 серпня 2019 р. 10:17

Здравствуйте!

Все работает, кроме одного.
Есть у меня ряд строковых локализуемых констант. Например:

static const QString V01 = "Вариант 01";
static const QString V02 = "Вариант 02";

Потом, они используются в мэпе:

QMap<int, QString> Variants =
{
    std::pair<int, QString> (1, V01)
  , std::pair<int, QString> (2, V02)
}

В конце есть функция, возвращающая сроку по коду:

QString variant(const int varCode)
{
    return Variants.value(varCode);
}

Как быть в такой ситуации? Константы в tr() не обернешь.

Сделал так:

static const QString V01 = QCoreApplication::tr("Вариант 01");
static const QString V02 = QCoreApplication::tr("Вариант 02");

Лингвист видит эти значения в контексте QCoreApplication,
позволяет их перевести, но доступа к переводу нет,
выводится на русском языке.
Что я делаю не так?

Evgenii Legotckoi
  • 13 серпня 2019 р. 10:43

Самая главная проблема в том, что у вас это константные переменные, и инициализируется они один единственный раз при запуске программы.
Поэтому делать динамический перевод в таком случае у вас не получится. Вам нужно иначе подходить к этой проблеме.

Напишите код так.

QString variant(const int varCode)
{
    switch (varCode)
    {
        case 1: return QCoreApplication::tr("Вариант 01");
        case 2: return QCoreApplication::tr("Вариант 02");
        default: return QString();
    }
}
grig_p
  • 14 серпня 2019 р. 01:27
  • (відредаговано)

Спасибо большое. Получилось

P
  • 19 листопада 2019 р. 14:10

Здравствуйте.
Спасибо за статью. Сделал перевод программы. Все работает.
Только я делал не динамический, но это пока и не надо.
Но с одной проблемой все же столкнулся.
В программе есть сцена. На ней создаются некие графические элементы.
в конструкторе элементов устанавливаю всплывающую подсказку,
например такую

this->setToolTip(tr("Кнопка"));

Все переводится кроме тултипов.
Не подскажете куда копать?

Evgenii Legotckoi
  • 20 листопада 2019 р. 02:50

Добрый день.

Вы используете QGraphicsItem? вообще побольшу бы кусок кода, чтобы иметь большее представление. Мысли есть, но не уверен

P
  • 20 листопада 2019 р. 03:10
  • (відредаговано)

да, есть классы наследуемые от QGraphicsItem.
в программе при нажатии определенных кнопок(слева на картинке), на сцене появляются Item-ы
с разными настройками. в конструкторе ни чего особого нет,только координаты,название,тултип.

перевод загружается так

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTranslator translator;
    translator.load(QString("Language_en"));
    a.installTranslator(&translator);
    MainWindow w;
    w.show();

    return a.exec();
}
Evgenii Legotckoi
  • 20 листопада 2019 р. 03:25
  • (відредаговано)

Вообще, метод tr является частью класса QObject, если он у вас используется в конструкторе, то вы должны были использовать множественное наследование для вашей кастомной кнопки. Поскольку графические объекты одни из немногих классов, которые не наследуются от QObject, кроме QGraphicsObject, но там также множественное наследование используется.

То есть заголовочный код вашей кнопки должен выглядеть так

class Button : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    /* и так далее */
}

Убедитесь, что у вас присутсвует макрос Q_OBJECT, он отвечает за мета информацию и включение возможности перевода.

Если же вы не наследовались от QObject, то перевод можно также включить с помощью другого макроса

class Button : public QGraphicsItem
{
    Q_DECLARE_TR_FUNCTIONS(Button)
public:
    /* и так далее */
}
Evgenii Legotckoi
  • 20 листопада 2019 р. 03:30

И в качестве дополнения из личного опыта. Пишите лучше все тексты на английском языке в функциях tr )))

P
  • 20 листопада 2019 р. 03:43

у меня множественное наследование.
сначала основной класс myitem

class myItem : public QObject, public QGraphicsItem
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    myItem(int x, int y, int w, int h);
    .....
    .....
    ..

со всякими переменными.

а потом от него все остальные , которые появляются на сцене.

"Пишите лучше все тексты на английском языке в функциях tr"
понимаю, просто программа очень спецефическая(это конфигуратор для устройства на микроконтроллере)
и не планировалась для других языков. но потом подтянулись иностранные граждане и попросили перевести..

Evgenii Legotckoi
  • 20 листопада 2019 р. 03:55

Покажите, как выглядит реализация конструктора. Поскольку на данный момент я не вижу причин, чтобы переводы не работали.

P
  • 20 листопада 2019 р. 04:02
myButton::myButton(int x, int y, int w, int h) : myItem (x, y, w, h)
{
  static int i = 1;
  myVar.text = "Btn" + QString::number(i++);
  myVar.myType = typeSimpleButton;
  this->setToolTip(tr("Кнопка"));
  myVar.func_read = 1;
  configVisibleWidgets |= 1<<visibleSettings::Hsize | 1<<visibleSettings::Wsize
      | 1<<visibleSettings::Color1 | 1<<visibleSettings::ColorText | 1<<visibleSettings::Color2
      | 1<<visibleSettings::TextEdit | 1<<visibleSettings::ModbusWrite
      | 1<<visibleSettings::Toggle | 1<<fontSizeSelect | 1<<itSlave;
}
myItem::myItem(int x, int y, int w, int h)
{
  myVar.rect.x =x;
  myVar.rect.y=y;
  myVar.rect.w=w;
  myVar.rect.h=h;
  setFlags(QGraphicsItem::ItemIsMovable |
           QGraphicsItem::ItemIsSelectable |
           QGraphicsRectItem::ItemIsFocusable);
}
P
  • 20 листопада 2019 р. 04:14

при этом тултипы заданные в редакторе форм переводятся...

Evgenii Legotckoi
  • 20 листопада 2019 р. 04:29

проблема в конструкторе, напишите так

myItem::myItem(int x, int y, int w, int h) : QObject()
{
  myVar.rect.x =x;
  myVar.rect.y=y;
  myVar.rect.w=w;
  myVar.rect.h=h;
  setFlags(QGraphicsItem::ItemIsMovable |
           QGraphicsItem::ItemIsSelectable |
           QGraphicsRectItem::ItemIsFocusable);
}
P
  • 20 листопада 2019 р. 05:05

добавил : QObject()
ни чего не изменилось.

ладно. что-нибудь придумаю

Evgenii Legotckoi
  • 20 листопада 2019 р. 05:08

Тогда хоть убейте, не знаю. Где-то не хватает или макроса или корректной реализации конструктора.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах