فصل هفتم: بازآفرینی کلاینت دسکتاپ Nextcloud: پارسی، جلالی، و امن

هدف فصل: کلاینتی که قلب پارسی دارد

تصور کنید کلاینت دسکتاپ Nextcloud شما به زبان پارسی سخن می‌گوید، تاریخ‌ها را با تقویم جلالی نمایش می‌دهد، و با احراز هویت محلی، امنیت را به سطح بالاتری می‌برد! در این فصل، کلاینت دسکتاپ Nextcloud (v3.17.3) را با فارسی‌سازی رابط کاربری، ادغام تقویم جلالی، و افزودن احراز هویت محلی مبتنی بر سیستم‌عامل ارتقا می‌دهیم. هدف؟ خلق یک تجربه بومی و امن برای کاربران ایرانی که همگام‌سازی فایل‌ها را به یک لذت پارسی تبدیل می‌کند. آماده‌ید تا کلاینت را به یک شاهکار تبدیل کنید؟


۷.۱ فارسی‌سازی: رابط کاربری به زبان پارسی

۷.۱.۱ ایجاد فایل ترجمه

فایل: desktop/translations/fa.ts

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fa_IR">
    <context>
        <name>MainWindow</name>
        <message>
            <source>Sync</source>
            <translation>همگام‌سازی</translation>
        </message>
        <message>
            <source>Settings</source>
            <translation>تنظیمات</translation>
        </message>
        <message>
            <source>File</source>
            <translation>فایل</translation>
        </message>
        <message>
            <source>Quit</source>
            <translation>خروج</translation>
        </message>
    </context>
    <context>
        <name>TrayIcon</name>
        <message>
            <source>Open Nextcloud</source>
            <translation>باز کردن Nextcloud</translation>
        </message>
        <message>
            <source>Syncing</source>
            <translation>در حال همگام‌سازی</translation>
        </message>
    </context>
</TS>

۷.۱.۲ کامپایل ترجمه‌ها

cd desktop
lrelease translations/fa.ts -o translations/fa.qm

۷.۱.۳ بارگذاری ترجمه‌ها

فایل: desktop/src/gui/main.cpp

#include <QApplication>
#include <QTranslator>

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

    QTranslator translator;
    if (translator.load("fa", ":/translations")) {
        app.installTranslator(&translator);
    }

    // ادامه کد...
    return app.exec();
}

تست سریع:

  • زبان سیستم را به fa_IR تغییر دهید.
  • کلاینت را اجرا کنید و مطمئن شوید متن‌ها به فارسی هستند.

۷.۲ جلالی‌سازی: نمایش تاریخ‌های شمسی

۷.۲.۱ تعریف کلاس جلالی

فایل: desktop/src/common/jalalidate.h

#ifndef JALALIDATE_H
#define JALALIDATE_H
#include <QDate>

class JalaliDate {
public:
    static QDate toJalali(const QDate& gregorian);
    static QDate toGregorian(int jy, int jm, int jd);
    static QString formatJalali(const QDate& gregorian, const QString& format = "yyyy/MM/dd");
};

#endif

فایل: desktop/src/common/jalalidate.cpp

#include "jalalidate.h"
#include <jdf.h>

QDate JalaliDate::toJalali(const QDate& gregorian) {
    int jy, jm, jd;
    gregorian_to_jalali(gregorian.year(), gregorian.month(), gregorian.day(), &jy, &jm, &jd);
    return QDate(jy, jm, jd);
}

QDate JalaliDate::toGregorian(int jy, int jm, int jd) {
    int gy, gm, gd;
    jalali_to_gregorian(jy, jm, jd, &gy, &gm, &gd);
    return QDate(gy, gm, gd);
}

QString JalaliDate::formatJalali(const QDate& gregorian, const QString& format) {
    int jy, jm, jd;
    gregorian_to_jalali(gregorian.year(), gregorian.month(), gregorian.day(), &jy, &jm, &jd);
    QString result = format;
    result.replace("yyyy", QString::number(jy, 10));
    result.replace("MM", QString::number(jm, 10).rightJustified(2, '0'));
    result.replace("dd", QString::number(jd, 10).rightJustified(2, '0'));
    return result;
}

وابستگی:

git clone https://github.com/ashkandi/jdf.git

۷.۲.۲ ادغام در رابط کاربری

فایل: desktop/src/gui/syncstatus.cpp

#include "jalalidate.h"
#include <QLabel>

void SyncStatus::updateStatus(const QDateTime& lastSync) {
    QString jalaliDate = JalaliDate::formatJalali(lastSync.date(), "yyyy/MM/dd HH:mm");
    statusLabel->setText(QString("آخرین همگام‌سازی: %1").arg(jalaliDate));
}

۷.۳ احراز هویت محلی: امنیت در دستان سیستم‌عامل

۷.۳.۱ تعریف دیالوگ احراز هویت

فایل: desktop/src/gui/localauthdialog.h

#ifndef LOCALAUTHDIALOG_H
#define LOCALAUTHDIALOG_H
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>

class LocalAuthDialog : public QDialog {
    Q_OBJECT
public:
    explicit LocalAuthDialog(QWidget* parent = nullptr);
    QString getCredentials() const;

private slots:
    void onAuthAttempt();

private:
    QLineEdit* usernameEdit;
    QLineEdit* passwordEdit;
    QPushButton* authButton;
};

#endif

فایل: desktop/src/gui/localauthdialog.cpp

#include "localauthdialog.h"
#include <QKeychain>
#include <QVBoxLayout>

LocalAuthDialog::LocalAuthDialog(QWidget* parent) : QDialog(parent) {
    setWindowTitle("احراز هویت محلی");

    usernameEdit = new QLineEdit(this);
    usernameEdit->setPlaceholderText("نام کاربری");
    passwordEdit = new QLineEdit(this);
    passwordEdit->setPlaceholderText("رمز عبور");
    passwordEdit->setEchoMode(QLineEdit::Password);
    authButton = new QPushButton("تأیید", this);

    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->addWidget(usernameEdit);
    layout->addWidget(passwordEdit);
    layout->addWidget(authButton);

    connect(authButton, &QPushButton::clicked, this, &LocalAuthDialog::onAuthAttempt);

    QKeychain::ReadPasswordJob job("nextcloud");
    job.setKey("user_credentials");
    connect(&job, &QKeychain::ReadPasswordJob::finished, this, [&](QKeychain::Job* job) {
        if (!job->error()) {
            passwordEdit->setText(job->textData());
        }
    });
    job.start();
}

void LocalAuthDialog::onAuthAttempt() {
    QKeychain::WritePasswordJob job("nextcloud");
    job.setKey("user_credentials");
    job.setTextData(passwordEdit->text());
    job.start();
    accept();
}

QString LocalAuthDialog::getCredentials() const {
    return QString("%1:%2").arg(usernameEdit->text(), passwordEdit->text());
}

۷.۳.۲ ادغام با کلاینت

فایل: desktop/src/gui/main.cpp

#include "localauthdialog.h"

void authenticateUser() {
    LocalAuthDialog dialog;
    if (dialog.exec() == QDialog::Accepted) {
        QString credentials = dialog.getCredentials();
        // استفاده از credentials
    }
}

وابستگی:

git clone https://github.com/frankosterfeld/qtkeychain.git

۷.۴ بیلد: آماده‌سازی کلاینت

  1. نصب وابستگی‌ها:
   sudo apt install qt5-default qt5keychain-dev libssl-dev
  1. بیلد پروژه:
   cd desktop
   mkdir build && cd build
   cmake -DCMAKE_BUILD_TYPE=Release ..
   make -j$(nproc)
  1. نصب:
   sudo make install

۷.۵ تست: لحظه حقیقت

  1. اجرای کلاینت:
   ./build/nextcloud
  1. تست فارسی‌سازی:
  2. زبان سیستم را به fa_IR تنظیم کنید.
  3. بررسی کنید که متن‌ها به فارسی هستند.

  4. تست جلالی‌سازی:

  5. تاریخ‌های همگام‌سازی را چک کنید (مثل ۱۴۰۴/۰۱/۰۱).

  6. تست احراز هویت:

  7. دیالوگ احراز هویت را باز کنید.
  8. credentials را در Keychain بررسی کنید.

تست‌های لبه:

  • سال کبیسه جلالی.
  • اجرای کلاینت بدون QtKeychain.
  • تغییر زبان به انگلیسی.

نکات حرفه‌ای

  • دسترسی‌پذیری: از Qt Accessibility Inspector استفاده کنید.
  • کشینگ تاریخ‌ها:
  static QMap<QString, QString> jalaliCache;
  QString JalaliDate::formatJalali(const QDate& gregorian, const QString& format) {
      QString key = QString("%1_%2").arg(gregorian.toString(), format);
      if (jalaliCache.contains(key)) return jalaliCache[key];
      QString result = /* محاسبات */;
      jalaliCache[key] = result;
      return result;
  }
  • مستندسازی: در README.fa.md توضیح دهید.
  • تجربه کاربر:
  QSettings settings("nextcloud", "client");
  settings.setValue("calendar_type", "jalali");