「Tolgee」協助您開發多語系APP

「Tolgee」是一款新興的翻譯輔助系統,可以協助您開發多語系APP。

目前最有名的雲端翻譯網站為 crowdin,功能強大的付費系統,很推薦使用;若公司需要自行架設(self-hosted),可以參考weblatetolgee,這兩樣都是較有名的免費開源翻譯系統。

以下是 WeblateTolgee 的比較表,以幫助你選擇適合的翻譯與本地化平台:

特性WeblateTolgee
類型自託管 + 雲端自託管 + 雲端
開源✅(完全開源)✅(開源,但部分功能需付費)
托管方式自託管 / Weblate 服務自託管 / Tolgee Cloud
即時協作
支援的格式40+ 種格式(JSON、YAML、PO、XLIFF、TS、ResX 等)JSON、XLIFF、PO、YAML、TS、Java Properties 等
機器翻譯支援 Google、DeepL、Azure、Apertium支援 Google、DeepL、Azure
翻譯記憶庫(TM)
自動提議翻譯
語言品質檢查✅(內建 QA 機制)✅(內建錯誤檢查)
上下文支援🟡(需手動提供)✅(可透過 Tolgee SDK 自動擷取)
API & SDKREST API,可與 Git、CI/CD 整合REST API,專有 SDK(適用於 React、Vue、Angular 等)
Git 整合✅(自動同步)✅(自動同步)
視覺化翻譯(In-context)🟡(有限支援)✅(內建即時預覽)
權限管理✅(細緻的角色與權限控制)✅(細緻的角色與權限控制)
價格免費(自託管)/ 付費雲端免費(基本功能)/ 進階功能需付費
適合對象開發團隊、開源專案、企業需要即時翻譯預覽的開發團隊

實作總結(推薦使用tolgee)

  • Weblate 需先選定APP翻譯格式,然後再進行翻譯,這樣可以直接與APP版控整合,但跨平台APP需要重複翻譯多次(多個格式),另外weblate本身語言翻譯很差(乾脆不要提供各國語系版),寧願使用英文版。
  • Tolgee 則採先翻譯方式,然後再整合不同APP開發格式輸出,這樣只要翻譯一次即可,再匯出讓工程師使用,但無法整合git較不方便。

ps. 到本日(2025/3/22) weblate尚未支援apple string catalog翻譯檔案(.xcstrings),很不方便;而tolgee 3/5之後支援了。

Android自動調整文字大小

我是個半吊子Android程式師,有時候常常被使用者罵文字版面跑掉,深深覺得andoird要能自動調整textview文字大小非常重要。

有些android使用者會調整手機預設字型大小,有些因為手機本身解析度不同,甚至摺疊手機,都會影響APP使用體驗;因此我們設計app一定要考慮文字大小。

官網已經有詳細的介紹,請點選這裡參考

我自己則常用以下設定來解決文字大小問題。

android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="20sp"
android:autoSizeMaxTextSize="24sp"
android:autoSizeStepGranularity="2sp"

Android取得目前wifi連線資料

API 31(含)以後,寫法變了,

https://developer.android.com/reference/kotlin/android/net/wifi/WifiManager#getConnectionInfo()
照著官方這樣寫,似乎還是無法取得連線資料,須加上 FLAG_INCLUDE_LOCATION_INFO 才行。

WifiManager wifiManager = (WifiManager) activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

final NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
ConnectivityManager connectivityManager = activity.getSystemService(ConnectivityManager.class);

ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
@Override
public void onAvailable(Network network) {}

@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
String aConnectedWifiSSD = wifiInfo.getSSID().toLowerCase();
Log.d(TAG,"wifiInfo="+wifiInfo);
connectivityManager.unregisterNetworkCallback( this);

}
};
if( wifiManager.isWifiEnabled() ) {
connectivityManager.requestNetwork(request, networkCallback); // For request
connectivityManager.registerNetworkCallback(request, networkCallback); // For listen
}
# 需要的權限
android.Manifest.permission#ACCESS_FINE_LOCATION
android.Manifest.permission#ACCESS_WIFI_STATE

Android開發關於AlertDialog兩三事

開發android都知道,要顯示視窗訊息,不是那直覺方便的,需要搭配Handler參與才能走得順。

# 原本以為myAlterDialog.show()後,就可以馬上執行其他程式商業邏輯,完成後停掉myAlterDialog即可
# (例如我們要實現APP「顯示"請稍後",然後背景執行其他程式商業邏輯,執行完取消"請稍後"」)
# 但我沒想到程式商業邏輯需要寫在Handler裡面才不會讓卡住。

public class MainActivity extends AppCompatActivity {
  AlertDialog.Builder waitBuilder = null;
  AlertDialog waitDialog;
  ...
  protected void onCreate(Bundle savedInstanceState) {
     ...
     waitBuilder = new AlertDialog.Builder(activity);
     waitBuilder.setCancelable(false);
     waitBuilder.setView(R.layout.loading_dialog);
     myAlterDialog = waitBuilder.create();
  }

  public void onStart() {

    adapter.setOnItemLongClickListener(.....){
      ...
      runOnUiThread(new Runnable() {
        @Override
	public void run() {
          myAlterDialog.show();
	  new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
	    @Override
 	    public void run() {
              runOnUiThread(new Runnable() {
                @Override
                public void run() {
                  ...
                  Toast.makeText(activity,"success"....
                  myAlterDialog.dismiss();
                }
              });
	    }
	  }, 300);	
        }
      }
      ...
     });		
   });
}

麻瓜勿擾,GIT文字筆記

2011年第一隻android app有數萬下載量,到2019年我又練習寫了一個「GIT文字筆記」,是個「麻瓜勿擾的APP」。
該APP有開源,久了我也幾乎忘了有這,最近收到app要改目標版本,否則要下架,於是我更新時,再加碼限制android 13或以上才能安裝使用;
果然幾乎沒人用,到目前只有50+下載 XD

我這APP也很奇葩,用git shallow減少clone下載版本量呢。

https://play.google.com/store/apps/details?id=inmethod.gitnotetaking

Android 13真真氣死人,APP欲取得外部檔案真實路徑有夠困難

後來我才知道 Android 10 (API Level 29) 已經預告取得外部檔案真實路徑的方式已經改

好險,有神人寫了好用的程式能「取得檔案的絕對路徑」,並且在 stackoverflow 說明原由,使用方式也超級方便的,請參找此神人的程式 https://github.com/HBiSoft/PickiT,一定不會失望而歸的。

「FydeOS – 为中国用户打造的ChromeOS」初體驗

公司希望Android撰寫的測試程式移植到windows平台上,但是這樣做很耗時,程式要重新開發; 因此我想到讓筆電或電腦桌機能夠直接執行android, 這樣就能解決問題了。

我排除已知的解決方案之一-Android模擬器、windows 11模擬andorid不考慮(因為要停用了)。

最後我希望類似chrome book 使用chrome OS直接支援android 的方式,沒想到還真的找到了 –FydeOS

安裝FydeOS有些眉角要注意的:

  1. 筆電-panasonic CF-NX3,12G Ram
  2. 官方安裝方式,我要連續安裝好幾次才成功
  3. 安裝時,我的舊電腦BIOS先調整非UEFI才能安裝,安裝後再調回UEFI,才能啟用開發者模式(v18版)
  4. 安裝時,我使用本機帳號,不線上註冊
  5. 安裝完畢,FydeOS系統成功開機,啟用Android位置權限失敗,後來發現安裝Open GApps,GApps會將Play商店裝起來,才能啟用位置權限
  6. Play商店無法登入(未通過Play防護安全認證)
  7. 其他方面很順,簡直就是chrome book

android Git文字筆記APP更新版本

3年前我實作一個Git文字筆記app, 使用jgit套件, 可將github clone到android手機上, 然後進行編輯, 編輯完畢,直接版控到github上面,

最近play store要求target api在2022年11月前, 至少要設定31 , 於是我就編輯了一下, 實在有夠麻煩, 一大堆套件要升級, android studio也是大改版要求舊的專案也要跟著升級.

升級升級, 想說之前jgit只使用4.5.7版本, 趁這機會,改用jgit 6.3.x版, 後來發現6.3.x 使用outputstream類別中的一個函式”transferTo”, 造成android app 找不到該method,程式跳掉.

最後我發現 android 13才支援”transferTo”

另外我開發的這支app, 當初硬改成可以接受未正式簽章的SSL網站, 現在變成資安問題,上架失敗, 原因是X509相關類別,被我繼承改成一律安全 , 只好再改成原本的檢查方式,

也就是說以後這支app要使用遠端git網站, 必須是正式簽章HTTPS, 否則是不能使用的.


為何我的app要使用jgit 6.3.x , 因為jgit終於支援shallow(depth)功能, 可以讓我clone的時候快一點, 不然有些commit數量太多, 要clone很久,很麻煩, 而app本身只想用來做文字筆記, 不用clone太多commit版本下來,不然使用者會嫌棄的.

公司有pixel手機, 趕緊升級到android 13

android 檢查play store是否安裝

java

public static boolean isPlayStoreInstalled(Context context){
    try {
        context.getPackageManager()
                .getPackageInfo(GooglePlayServicesUtil.GOOGLE_PLAY_STORE_PACKAGE, 0);
        return true;
    } catch (PackageManager.NameNotFoundException e) {
        return false;
    }
}

kotlin

fun isPlayStoreInstalled(context: Context): Boolean {
    return try {
        context.packageManager
            .getPackageInfo(GooglePlayServicesUtil.GOOGLE_PLAY_STORE_PACKAGE, 0)
        true
    } catch (e: PackageManager.NameNotFoundException) {
        false
    }
}

build.gradle

    implementation 'com.google.android.gms:play-services-base:18.0.1'
1 2 3 4