Skip to content

Commit e7173a5

Browse files
committed
Implement OTA updates of translations
Check for and download updated translations periodically. Over-the-Air updates like this decouple translations from Poedit code releases and allow both faster updates and proof-reading / testing of translations directly in the app.
1 parent e58c8d1 commit e7173a5

File tree

6 files changed

+100
-1
lines changed

6 files changed

+100
-1
lines changed

deps/boost/Boost.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
B2167C331815ADF0003A1428 /* portability.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B2167C2C1815ADF0003A1428 /* portability.cpp */; };
1616
B2167C341815ADF0003A1428 /* unique_path.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B2167C2D1815ADF0003A1428 /* unique_path.cpp */; };
1717
B2167C351815ADF0003A1428 /* utf8_codecvt_facet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B2167C2E1815ADF0003A1428 /* utf8_codecvt_facet.cpp */; };
18+
B24D1D092AE95B3A00FB31D9 /* gzip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B24D1D082AE95B3A00FB31D9 /* gzip.cpp */; };
1819
B287410C1815AF6000C4051F /* future.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B28741061815AF6000C4051F /* future.cpp */; };
1920
B287410D1815AF6000C4051F /* once.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B28741081815AF6000C4051F /* once.cpp */; };
2021
B287410F1815AF6000C4051F /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B287410A1815AF6000C4051F /* thread.cpp */; };
@@ -53,6 +54,7 @@
5354
B2167C2E1815ADF0003A1428 /* utf8_codecvt_facet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = utf8_codecvt_facet.cpp; path = libs/filesystem/src/utf8_codecvt_facet.cpp; sourceTree = "<group>"; };
5455
B2167C3B1815AEA5003A1428 /* libboost_thread.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libboost_thread.a; sourceTree = BUILT_PRODUCTS_DIR; };
5556
B228A1561AD95CB9006B0BBC /* third_party.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = third_party.xcconfig; path = ../custom_build/third_party.xcconfig; sourceTree = "<group>"; };
57+
B24D1D082AE95B3A00FB31D9 /* gzip.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = gzip.cpp; path = libs/iostreams/src/gzip.cpp; sourceTree = "<group>"; };
5658
B28741061815AF6000C4051F /* future.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = future.cpp; path = libs/thread/src/future.cpp; sourceTree = "<group>"; };
5759
B28741081815AF6000C4051F /* once.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = once.cpp; sourceTree = "<group>"; };
5860
B287410A1815AF6000C4051F /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = thread.cpp; sourceTree = "<group>"; };
@@ -230,6 +232,7 @@
230232
B287414D1815B0FB00C4051F /* file_descriptor.cpp */,
231233
B287414E1815B0FB00C4051F /* mapped_file.cpp */,
232234
B287414F1815B0FB00C4051F /* zlib.cpp */,
235+
B24D1D082AE95B3A00FB31D9 /* gzip.cpp */,
233236
);
234237
name = boost_iostreams;
235238
sourceTree = "<group>";
@@ -497,6 +500,7 @@
497500
B28741511815B0FB00C4051F /* mapped_file.cpp in Sources */,
498501
B28741501815B0FB00C4051F /* file_descriptor.cpp in Sources */,
499502
B28741521815B0FB00C4051F /* zlib.cpp in Sources */,
503+
B24D1D092AE95B3A00FB31D9 /* gzip.cpp in Sources */,
500504
);
501505
runOnlyForDeploymentPostprocessing = 0;
502506
};

src/configuration.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,18 @@ void Config::Write(const std::string& key, bool value)
259259
wxConfig::Get()->Write(key, value);
260260
}
261261

262+
bool Config::Read(const std::string& key, long *out)
263+
{
264+
CfgLock lock;
265+
return wxConfig::Get()->Read(key, out);
266+
}
267+
268+
void Config::Write(const std::string& key, long value)
269+
{
270+
CfgLock lock;
271+
wxConfig::Get()->Write(key, value);
272+
}
273+
262274

263275
::PretranslateSettings Config::PretranslateSettings()
264276
{

src/configuration.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ class Config
7373
static std::string LocalazyMetadata() { return Read("/accounts/localazy/metadata", std::string()); }
7474
static void LocalazyMetadata(const std::string& prj) { return Write("/accounts/localazy/metadata", prj); }
7575

76+
static time_t OTATranslationLastCheck() { return Read("/ota/last_check", (long)0); }
77+
static void OTATranslationLastCheck(time_t when) { Write("/ota/last_check", (long)when); }
78+
79+
static std::string OTATranslationEtag() { return Read("/ota/etag", std::string()); }
80+
static void OTATranslationEtag(const std::string& etag) { Write("/ota/etag", etag); }
81+
7682
private:
7783
template<typename T>
7884
static T Read(const std::string& key, T defval)
@@ -87,10 +93,12 @@ class Config
8793
static bool Read(const std::string& key, std::string *out);
8894
static bool Read(const std::string& key, std::wstring *out);
8995
static bool Read(const std::string& key, bool *out);
96+
static bool Read(const std::string& key, long *out);
9097

9198
static void Write(const std::string& key, const std::string& value);
9299
static void Write(const std::string& key, const std::wstring& value);
93100
static void Write(const std::string& key, bool value);
101+
static void Write(const std::string& key, long value);
94102
};
95103

96104
#endif // Poedit_configuration_h

src/edapp.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
#include <wx/ipc.h>
4848
#include <wx/translation.h>
4949

50+
#include <iostream>
51+
#include <fstream>
52+
#include <boost/iostreams/copy.hpp>
53+
#include <boost/iostreams/filtering_stream.hpp>
54+
#include <boost/iostreams/filter/gzip.hpp>
55+
5056
#ifdef __WXOSX__
5157
#include "macos_helpers.h"
5258
#endif
@@ -584,7 +590,7 @@ void PoeditApp::SetupLanguage()
584590
if (langinfo)
585591
{
586592
language = langinfo->Language;
587-
uilang.clear(); // will go through locale
593+
// keep 'uilang' for use below
588594
}
589595
}
590596
#endif
@@ -623,8 +629,69 @@ void PoeditApp::SetupLanguage()
623629
#ifdef __WXMSW__
624630
win_sparkle_set_lang(bestTrans.utf8_str());
625631
#endif
632+
633+
#ifdef SUPPORTS_OTA_UPDATES
634+
SetupOTALanguageUpdate(trans, str::to_utf8(bestTrans));
635+
#endif
626636
}
627637

638+
#ifdef SUPPORTS_OTA_UPDATES
639+
void PoeditApp::SetupOTALanguageUpdate(wxTranslations *trans, const std::string& lang)
640+
{
641+
if (lang == "en")
642+
return;
643+
644+
// use downloaded OTA translations:
645+
auto dir = GetCacheDir("Translations") + "/" + POEDIT_VERSION;
646+
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(dir);
647+
trans->AddCatalog("poedit-ota");
648+
649+
// ..and update them (but at most once a day):
650+
auto lastCheck = Config::OTATranslationLastCheck();
651+
auto now = time(NULL);
652+
if (now < lastCheck + 24*60*60)
653+
return;
654+
Config::OTATranslationLastCheck(now);
655+
656+
wxFileName mofile(dir + "/" + lang + "/poedit-ota.mo");
657+
wxFileName::Mkdir(mofile.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
658+
659+
http_client::headers hdrs;
660+
std::string etag;
661+
if (mofile.FileExists())
662+
etag = Config::OTATranslationEtag();
663+
if (!etag.empty())
664+
hdrs.emplace_back("If-None-Match", etag);
665+
666+
http_client::download_from_anywhere("https://ota.poedit.net/i18n/" POEDIT_VERSION "/" + lang + "/poedit-ota.mo.gz", hdrs)
667+
.then_on_main([=](downloaded_file f)
668+
{
669+
TempOutputFileFor temp(mofile);
670+
std::ofstream output_file(temp.FileName().fn_str(), std::ios_base::binary);
671+
std::ifstream input_file(f.filename().GetFullPath().mb_str(), std::ios_base::binary);
672+
673+
if (output_file.fail() || input_file.fail())
674+
return;
675+
676+
boost::iostreams::filtering_istream input;
677+
input.push(boost::iostreams::gzip_decompressor());
678+
input.push(input_file);
679+
680+
boost::iostreams::copy(input, output_file);
681+
input_file.close();
682+
output_file.close();
683+
684+
temp.Commit();
685+
Config::OTATranslationEtag(f.etag());
686+
687+
// re-add the catalog to force reloading updated file
688+
trans->AddCatalog("poedit-ota");
689+
})
690+
.catch_all([](dispatch::exception_ptr){});
691+
}
692+
#endif // SUPPORTS_OTA_UPDATES
693+
694+
628695
wxLayoutDirection PoeditApp::GetLayoutDirection() const
629696
{
630697
return g_layoutDirection;

src/edapp.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
class WXDLLIMPEXP_FWD_BASE wxConfigBase;
4040
class WXDLLIMPEXP_FWD_BASE wxSingleInstanceChecker;
4141

42+
#if defined(HAVE_HTTP_CLIENT) && (defined(__WXMSW__) || defined(__WXOSX__))
43+
#define SUPPORTS_OTA_UPDATES
44+
#endif
4245

4346
/// wxApp for use with
4447
class PoeditApp : public wxApp, public MenusManager
@@ -104,6 +107,9 @@ class PoeditApp : public wxApp, public MenusManager
104107
void HandleCustomURI(const wxString& uri);
105108

106109
void SetupLanguage();
110+
#ifdef SUPPORTS_OTA_UPDATES
111+
void SetupOTALanguageUpdate(wxTranslations *trans, const std::string& lang);
112+
#endif
107113

108114
// App-global menu commands:
109115
void OnNewFromScratch(wxCommandEvent& event);

src/utility.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ class TempOutputFileFor
253253
{
254254
public:
255255
explicit TempOutputFileFor(const wxString& filename);
256+
explicit TempOutputFileFor(const wxFileName& filename) : TempOutputFileFor(filename.GetFullPath()) {}
257+
256258
~TempOutputFileFor();
257259

258260
/// Name of the temporary placeholder

0 commit comments

Comments
 (0)