参照 Flutter 2.8.1本地化/国际化应用程序名称 可以实现 Android/macOS/iOS/Web 的应用名称相关的国际化。但是在 Linux 应用上如何相同的功能,目前暂时没有一个统一的标准。
研究了许久,终于基本上算是搞定,解决方案如下:
使用 gettext 来实现国际化相关的功能。
首先配置,调整工程的目录如下:
project/
project/linux
project/linux/flutter
project/linux/flutter/CMakeLists.txt
project/linux/locale/en_US/app.po
project/linux/locale/zh_CN/app.mo
project/linux/locale/CMakeLists.txt
project/linux/CMakeLists.txt
project/linux/main.cc
project/linux/my_application.cc
project/linux/my_application.h
对应语言 i18n 相关配置文件的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(OUTPUT_NAME "app") set(LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(LOCALE_BUILD_DIR "${CMAKE_BINARY_DIR}/locale") set(LOCALE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/locale") # Setting up Internationalisation (i18n) find_package (Intl REQUIRED) if (Intl_FOUND) message(STATUS "Internationalization (i18n) found:") message(STATUS " INTL_INCLUDE_DIRS: ${Intl_INCLUDE_DIRS}") message(STATUS " INTL_LIBRARIES: ${Intl_LIBRARIES}") message(STATUS " Version: ${Intl_VERSION}") include_directories(${Intl_INCLUDE_DIRS}) link_directories(${Intl_LIBRARY_DIRS}) else () message(STATUS "Internationalization (i18n) Not found!") endif () find_package(Gettext REQUIRED) if (Gettext_FOUND) message(STATUS "Gettext found:") message(STATUS " Version: ${GETTEXT_VERSION_STRING}") else () message(STATUS "Gettext Not found!") endif () find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) find_program(GETTEXT_MSGMERGE_EXECUTABLE msgmerge) find_program(GETTEXT_MSGFMT_EXECUTABLE msgfmt) if (GETTEXT_XGETTEXT_EXECUTABLE) message(DEBUG " xgettext: ${GETTEXT_XGETTEXT_EXECUTABLE}") file(GLOB_RECURSE PO_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/*.po) add_custom_target( pot-update COMMENT "pot-update: Done." DEPENDS ${LOCALE_DIR}/${OUTPUT_NAME}.pot ) add_custom_command( TARGET pot-update PRE_BUILD COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=utf-8 --force-po --output=${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot --keyword=_ --width=80 ${PO_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "pot-update: Pot file generated: ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot" ) else () message(STATUS "pot-update not created!") endif (GETTEXT_XGETTEXT_EXECUTABLE) if (GETTEXT_MSGMERGE_EXECUTABLE) message(DEBUG " msgmerge: ${GETTEXT_MSGMERGE_EXECUTABLE}") add_custom_target( pot-merge COMMENT "pot-merge: Done." DEPENDS ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot ) file(GLOB PO_FILES ${LOCALE_DIR}/*/${OUTPUT_NAME}.po) message(TRACE " PO_FILES: ${PO_FILES}") foreach(PO_FILE IN ITEMS ${PO_FILES}) message(DEBUG " Adding msgmerge for: ${PO_FILE}") add_custom_command( TARGET pot-merge PRE_BUILD COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} ${PO_FILE} ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot COMMENT "pot-merge: ${PO_FILE}" ) endforeach() else () message(STATUS "pot-merge not created!") endif (GETTEXT_MSGMERGE_EXECUTABLE) if (GETTEXT_MSGFMT_EXECUTABLE) message(DEBUG " msgmerge: ${GETTEXT_MSGFMT_EXECUTABLE}") file(GLOB PO_LANGS LIST_DIRECTORIES true ${LOCALE_DIR}/*) message(TRACE " PO_LANGS: ${PO_LANGS}") add_custom_target( po-compile COMMENT "po-compile: Done." ) foreach(PO_LANG IN ITEMS ${PO_LANGS}) if(IS_DIRECTORY ${PO_LANG}) message(STATUS " Adding msgfmt for: ${PO_LANG}") file(RELATIVE_PATH REL_PO_LANG "${LOCALE_DIR}" "${PO_LANG}") set(MO_BUILD_DIR "${LOCALE_BUILD_DIR}/${REL_PO_LANG}") file(MAKE_DIRECTORY "${MO_BUILD_DIR}") set(MO_NAME "${MO_BUILD_DIR}/${OUTPUT_NAME}.mo") add_custom_command( TARGET po-compile PRE_BUILD COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} --output-file=${MO_NAME} ${OUTPUT_NAME}.po WORKING_DIRECTORY "${PO_LANG}" COMMENT "po-compile: ${PO_LANG}" ) install(FILES "${LOCALE_BUILD_DIR}/${REL_PO_LANG}/${OUTPUT_NAME}.mo" DESTINATION "${LOCALE_INSTALL_DIR}/${REL_PO_LANG}/LC_MESSAGES" COMPONENT Runtime) endif() endforeach() else () message(STATUS "pot-compile not created!") endif (GETTEXT_MSGFMT_EXECUTABLE) |
接下来,修改 Linux 工程的配置文件,增加对 本地化(i18n) 文件的引用,在合适的位置增加如下代码:
1 2 3 |
# locale build and install set(FLUTTER_LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/locale") add_subdirectory(${FLUTTER_LOCALE_DIR}) |
完整的代码参考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# Project-level configuration. cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "abc") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "xxx.xxx.xxx") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, # not the value here, or `flutter run` will no longer work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} po-compile) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # locale build and install set(FLUTTER_LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/locale") add_subdirectory(${FLUTTER_LOCALE_DIR}) # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() |
使用多语言的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
#include "my_application.h" #include <flutter_linux/flutter_linux.h> #ifdef GDK_WINDOWING_X11 #include <gdk/gdkx.h> #endif #include <sys/stat.h> #include <libgen.h> #include <locale.h> #include <libintl.h> #include "flutter/generated_plugin_registrant.h" #define LOCALE_DIR "/locale/" #define PACKAGE "app" #define _(String) gettext(String) struct _MyApplication { GtkApplication parent_instance; char **dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // 118n static void setup_app_locale() { struct stat sb; const char *proc_name = "/proc/self/exe"; if (lstat(proc_name, &sb) >= 0) { /* Add one to the link size, so that we can determine whether the buffer returned by readlink() was truncated. */ ssize_t bufsiz = sb.st_size + 1; /* Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero. In that case, take PATH_MAX as a "good enough" estimate. */ if (0 == sb.st_size) { bufsiz = PATH_MAX; } ssize_t loc_dir_len = bufsiz + strlen(LOCALE_DIR) + 1; char *buf = (char *)malloc(loc_dir_len); if (NULL != buf) { ssize_t nbytes = readlink(proc_name, buf, bufsiz); if (nbytes >= 0) { /* If the return value was equal to the buffer size, then the the link target was larger than expected (perhaps because the target was changed between the call to lstat() and the call to readlink()). Warn the user that the returned target may have been truncated. */ if (nbytes == bufsiz) { g_message("(Returned buffer may have been truncated)\n"); } if (nbytes <= bufsiz) { buf[nbytes] = '\0'; } char *dir = dirname(buf); strncat(dir, LOCALE_DIR, strlen(LOCALE_DIR)); char *loc = setlocale(LC_ALL, NULL); g_message("current locale is:%s", loc); g_message("locale files dir is:%s", buf); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, buf); textdomain(PACKAGE); } free(buf); } } } // Implements GApplication::activate. static void my_application_activate(GApplication *application) { MyApplication *self = MY_APPLICATION(application); GtkWindow *window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; setup_app_locale(); #ifdef GDK_WINDOWING_X11 GdkScreen *screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, _("app_name")); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, _("app_name")); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView *view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) { MyApplication *self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject *object) { MyApplication *self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass *klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication *self) {} MyApplication *my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } |