Add API endpoint to retrieve custom languages and complete language pack
This commit is contained in:
parent
8257eb7aa2
commit
e29b86e4dc
27
include/I18n.h
Normal file
27
include/I18n.h
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
#include <WString.h>
|
||||
#include <list>
|
||||
|
||||
struct LanguageInfo_t {
|
||||
String code;
|
||||
String name;
|
||||
String filename;
|
||||
};
|
||||
|
||||
class I18nClass {
|
||||
public:
|
||||
I18nClass();
|
||||
void init(Scheduler& scheduler);
|
||||
std::list<LanguageInfo_t> getAvailableLanguages();
|
||||
|
||||
private:
|
||||
void readLangPacks();
|
||||
void readConfig(String file);
|
||||
|
||||
std::list<LanguageInfo_t> _availLanguages;
|
||||
};
|
||||
|
||||
extern I18nClass I18n;
|
||||
@ -11,4 +11,5 @@ public:
|
||||
static int getTimezoneOffset();
|
||||
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
|
||||
static void removeAllFiles();
|
||||
static String generateMd5FromFile(String file);
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "WebApi_file.h"
|
||||
#include "WebApi_firmware.h"
|
||||
#include "WebApi_gridprofile.h"
|
||||
#include "WebApi_i18n.h"
|
||||
#include "WebApi_inverter.h"
|
||||
#include "WebApi_limit.h"
|
||||
#include "WebApi_maintenance.h"
|
||||
@ -53,6 +54,7 @@ private:
|
||||
WebApiFileClass _webApiFile;
|
||||
WebApiFirmwareClass _webApiFirmware;
|
||||
WebApiGridProfileClass _webApiGridprofile;
|
||||
WebApiI18nClass _webApiI18n;
|
||||
WebApiInverterClass _webApiInverter;
|
||||
WebApiLimitClass _webApiLimit;
|
||||
WebApiMaintenanceClass _webApiMaintenance;
|
||||
|
||||
14
include/WebApi_i18n.h
Normal file
14
include/WebApi_i18n.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
class WebApiI18nClass {
|
||||
public:
|
||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||
|
||||
private:
|
||||
void onI18nLanguages(AsyncWebServerRequest* request);
|
||||
void onI18nLanguage(AsyncWebServerRequest* request);
|
||||
};
|
||||
@ -108,3 +108,5 @@
|
||||
#define LED_BRIGHTNESS 100U
|
||||
|
||||
#define MAX_INVERTER_LIMIT 2250
|
||||
|
||||
#define LANG_PACK_SUFFIX ".lang.json"
|
||||
|
||||
72
src/I18n.cpp
Normal file
72
src/I18n.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2024 Thomas Basler and others
|
||||
*/
|
||||
#include "I18n.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "Utils.h"
|
||||
#include "defaults.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
I18nClass I18n;
|
||||
|
||||
I18nClass::I18nClass()
|
||||
{
|
||||
}
|
||||
|
||||
void I18nClass::init(Scheduler& scheduler)
|
||||
{
|
||||
readLangPacks();
|
||||
}
|
||||
|
||||
std::list<LanguageInfo_t> I18nClass::getAvailableLanguages()
|
||||
{
|
||||
return _availLanguages;
|
||||
}
|
||||
|
||||
void I18nClass::readLangPacks()
|
||||
{
|
||||
auto root = LittleFS.open("/");
|
||||
auto file = root.getNextFileName();
|
||||
|
||||
while (file != "") {
|
||||
if (file.endsWith(LANG_PACK_SUFFIX)) {
|
||||
MessageOutput.printf("Read File %s\r\n", file.c_str());
|
||||
readConfig(file);
|
||||
}
|
||||
file = root.getNextFileName();
|
||||
}
|
||||
root.close();
|
||||
}
|
||||
|
||||
void I18nClass::readConfig(String file)
|
||||
{
|
||||
JsonDocument filter;
|
||||
filter["meta"] = true;
|
||||
|
||||
File f = LittleFS.open(file, "r", false);
|
||||
|
||||
JsonDocument doc;
|
||||
|
||||
// Deserialize the JSON document
|
||||
const DeserializationError error = deserializeJson(doc, f, DeserializationOption::Filter(filter));
|
||||
if (error) {
|
||||
MessageOutput.printf("Failed to read file %s\r\n", file.c_str());
|
||||
f.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LanguageInfo_t lang;
|
||||
lang.code = String(doc["meta"]["code"]);
|
||||
lang.name = String(doc["meta"]["name"]);
|
||||
lang.filename = file;
|
||||
|
||||
_availLanguages.push_back(lang);
|
||||
|
||||
f.close();
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
#include "MessageOutput.h"
|
||||
#include "PinMapping.h"
|
||||
#include <LittleFS.h>
|
||||
#include <MD5Builder.h>
|
||||
|
||||
uint32_t Utils::getChipId()
|
||||
{
|
||||
@ -80,3 +81,34 @@ void Utils::removeAllFiles()
|
||||
file = root.getNextFileName();
|
||||
}
|
||||
}
|
||||
|
||||
String Utils::generateMd5FromFile(String file)
|
||||
{
|
||||
if (!LittleFS.exists(file)) {
|
||||
return String();
|
||||
}
|
||||
|
||||
File f = LittleFS.open(file, "r");
|
||||
if (!file) {
|
||||
return String();
|
||||
}
|
||||
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
|
||||
// Read the file in chunks to avoid using too much memory
|
||||
const size_t bufferSize = 512;
|
||||
uint8_t buffer[bufferSize];
|
||||
|
||||
while (f.available()) {
|
||||
size_t bytesRead = f.read(buffer, bufferSize);
|
||||
md5.add(buffer, bytesRead);
|
||||
}
|
||||
|
||||
// Finalize and calculate the MD5 hash
|
||||
md5.calculate();
|
||||
|
||||
f.close();
|
||||
|
||||
return md5.toString();
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ void WebApiClass::init(Scheduler& scheduler)
|
||||
_webApiFile.init(_server, scheduler);
|
||||
_webApiFirmware.init(_server, scheduler);
|
||||
_webApiGridprofile.init(_server, scheduler);
|
||||
_webApiI18n.init(_server, scheduler);
|
||||
_webApiInverter.init(_server, scheduler);
|
||||
_webApiLimit.init(_server, scheduler);
|
||||
_webApiMaintenance.init(_server, scheduler);
|
||||
|
||||
81
src/WebApi_i18n.cpp
Normal file
81
src/WebApi_i18n.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2024 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_i18n.h"
|
||||
#include "I18n.h"
|
||||
#include "Utils.h"
|
||||
#include "WebApi.h"
|
||||
#include <AsyncJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "MessageOutput.h"
|
||||
|
||||
void WebApiI18nClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
|
||||
server.on("/api/i18n/languages", HTTP_GET, std::bind(&WebApiI18nClass::onI18nLanguages, this, _1));
|
||||
server.on("/api/i18n/language", HTTP_GET, std::bind(&WebApiI18nClass::onI18nLanguage, this, _1));
|
||||
}
|
||||
|
||||
void WebApiI18nClass::onI18nLanguages(AsyncWebServerRequest* request)
|
||||
{
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(true);
|
||||
auto& root = response->getRoot();
|
||||
const auto& languages = I18n.getAvailableLanguages();
|
||||
|
||||
for (auto& language : languages) {
|
||||
auto jsonLang = root.add<JsonObject>();
|
||||
|
||||
jsonLang["code"] = language.code;
|
||||
jsonLang["name"] = language.name;
|
||||
}
|
||||
|
||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
void WebApiI18nClass::onI18nLanguage(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (request->hasParam("code")) {
|
||||
String code = request->getParam("code")->value();
|
||||
|
||||
const auto& languages = I18n.getAvailableLanguages();
|
||||
auto it = std::find_if(languages.begin(), languages.end(), [code](const LanguageInfo_t& elem) {
|
||||
return elem.code == code;
|
||||
});
|
||||
|
||||
if (it != languages.end()) {
|
||||
String md5 = Utils::generateMd5FromFile(it->filename);
|
||||
|
||||
String expectedEtag;
|
||||
expectedEtag = "\"";
|
||||
expectedEtag += md5;
|
||||
expectedEtag += "\"";
|
||||
|
||||
bool eTagMatch = false;
|
||||
if (request->hasHeader("If-None-Match")) {
|
||||
const AsyncWebHeader* h = request->getHeader("If-None-Match");
|
||||
eTagMatch = h->value().equals(expectedEtag);
|
||||
}
|
||||
|
||||
// begin response 200 or 304
|
||||
AsyncWebServerResponse* response;
|
||||
if (eTagMatch) {
|
||||
response = request->beginResponse(304);
|
||||
} else {
|
||||
response = request->beginResponse(LittleFS, it->filename, asyncsrv::T_application_json);
|
||||
}
|
||||
|
||||
// HTTP requires cache headers in 200 and 304 to be identical
|
||||
response->addHeader("Cache-Control", "public, must-revalidate");
|
||||
response->addHeader("ETag", expectedEtag);
|
||||
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
#include "Configuration.h"
|
||||
#include "Datastore.h"
|
||||
#include "Display_Graphic.h"
|
||||
#include "I18n.h"
|
||||
#include "InverterSettings.h"
|
||||
#include "Led_Single.h"
|
||||
#include "MessageOutput.h"
|
||||
@ -24,9 +25,9 @@
|
||||
#include "defaults.h"
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <SpiManager.h>
|
||||
#include <TaskScheduler.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <SpiManager.h>
|
||||
|
||||
#include <driver/uart.h>
|
||||
|
||||
@ -83,6 +84,11 @@ void setup()
|
||||
auto& config = Configuration.get();
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Read languate pack
|
||||
MessageOutput.print("Reading language pack... ");
|
||||
I18n.init(scheduler);
|
||||
MessageOutput.println("done");
|
||||
|
||||
// Load PinMapping
|
||||
MessageOutput.print("Reading PinMapping... ");
|
||||
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user