Ngay từ đầu khi được thiết kế, EzyPlatform đã hướng tới mục tiêu phục vụ đa quốc gia, nghĩa là sẽ cần cung cấp giao diện với nhiều ngôn ngữ khác nhau và điều này đặt ra những thách thức không nhỏ cho Young Monkeys.

EzyPlatform được thừa hưởng từ EzyHTTP

Bản thân EzyPlatform không thực sự cung cấp thuật toán và các thành phần để quản lý đa ngôn ngữ mà nó được thừa hưởng từ EzyHTTP.

EzyPlatform quản lý đa ngôn ngữ thế nào.png

EzyPlatform chỉ đơn giản là cung cấp đường dẫn đến thư mục resources/messages của thành phần của các plugin hay theme mà nó quản lý cho EzyHTTP sau đó sử dụng các lớp của EzyHTTP để đọc messages và trả về cho EzyHTTP.

Có một điều thú vị ở đây là EzyPlatform lại không điều khiển EzyPlatform, nghĩa là không trực tiếp gọi đến EzyHTTP mà cài đặt một giao diện có tên MessageProvider, đây là một trong những ứng dụng thú vị của Đảo ngược điều khiển - IoC.

Quá trình các message đa ngôn ngữ được đưa vào quản lý

Các tập tin message sẽ ở dạng key value, ví dụ:

copy_url=Copy URL
cpu_usage=CPU usage
search_result.title=Search Result
server_error.title=Server Error
Chú ý: Chúng tôi khuyến khích bạn sử dụng các key ở dạng snake_case hoặc dot.case cho thống nhất.

Đối với các ngôn ngữ khác nhau sẽ lưu ở các file có dạng messages_[mã ngôn ngữ].properties ví dụ:

  • messages_vi.properties: dành cho tiếng Việt.
  • messages_zh.properties: dành cho tiếng Trung.

Mặc định thì sẽ lấy các các message ở tập tin messages.properties. Nếu không tìm thấy tin nhắn nào thì giá trị sẽ được lấy bằng cách chuyển đổi từ key sang value, ví dụ hello world sẽ được chuyển thành Hello World.

Chú ý: Nếu bạn truyền một key rỗng sẽ gây ra lỗi, vậy nên hãy đảm bảo rằng bạn sẽ luôn truyền một key có độ dài ít nhất là 1.

Các tập tin messages sẽ bắt buộc phải đặt trong thư mục resources/messages của các plugin hay theme.

Các bước để đưa message vào quản lý sẽ như sau:

  1. Vì admin và web tồn tại độc lập nên các message cũng được quản lý độc lập.
  2. EzyPlatform sẽ cung cấp đường dẫn của thư mục resources/messages của thành phần hiện tại là admin web.
  3. EzyPlatform sử dụng lớp MessageReader của EzyHTTP để đọc toàn bộ các messages của các ngôn ngữ.
  4. EzyPlatform duyệt qua toàn bộ các plugin và theme đã được kích hoạt và cung cấp đường dẫn đến thư mục resources/messages của các plugin hay theme này và lại sử dụng MessageReader để đọc.
  5. Cuối cùng là tổng hợp thành một Map<String, Properties> giữa ngôn ngữ và các message để trả lại cho EzyHTTP.
  6. EzyHTTP sẽ đưa các message vào quản lý.

Bạn có thể tham khảo mã nguồn của lớp AbstractMessageProvider để biết chi tiết hơn các bước:

@AllArgsConstructor
public abstract class AbstractMessageProvider implements MessageProvider {

    private final FileSystemManager fileSystemManager;
    private static final String MESSAGES_FOLDER = "resources/messages";

    @Override
    public Map<String, Properties> provide() {
        String ezyplatformHomePath = fileSystemManager.getEzyHomePathString();
        Map<String, Properties> answer = new HashMap<>();
        TargetType targetType = getInclusiveTargetType();
        readAndAppendMessages(
            answer,
            Paths.get(
                ezyplatformHomePath,
                targetType.getName(),
                MESSAGES_FOLDER
            )
        );
        ModuleType[] moduleTypes = getInclusiveModuleTypes();
        Map<ModuleType, List<String>> modulesMap = getInclusiveModulesMapByModuleTypes(
            moduleTypes
        );
        for (ModuleType moduleType : moduleTypes) {
            List<String> modules = modulesMap.getOrDefault(
                moduleType,
                Collections.emptyList()
            );
            for (String module : modules) {
                readAndAppendMessages(
                    answer,
                    Paths.get(
                        ezyplatformHomePath,
                        moduleType.getTargetFolder(),
                        module,
                        MESSAGES_FOLDER
                    )
                );
            }
        }
        return answer;
    }

    private void readAndAppendMessages(
        Map<String, Properties> messages,
        Path messageFolder
    ) {
        if (!Files.exists(messageFolder)) {
            return;
        }
        MessageReader messageReader = MessageReader.getDefault();
        Map<String, Properties> map = messageReader.read(
            messageFolder.toString()
        );
        for (Map.Entry<String, Properties> e : map.entrySet()) {
            String lang = e.getKey();
            messages
                .computeIfAbsent(lang, k -> new Properties())
                .putAll(e.getValue());
        }
    }

    protected abstract TargetType getInclusiveTargetType();

    protected abstract ModuleType[] getInclusiveModuleTypes();

    protected abstract Map<ModuleType, List<String>> getInclusiveModulesMapByModuleTypes(
        ModuleType[] moduleTypes
    );
}

Bên trong EzyHTTP quản lý đã ngôn ngữ thế nào?

Cũng rất đơn giản thôi như bạn vừa thấy, EzyHTTP sẽ tổng hợp tất cả các message từ các MessageProvider và đưa vào một Map<String, Properties> duy nhất ánh xạ giữa ngôn ngữ và các message, mã nguồn tổng hợp sẽ kiểu thế này:

private Map<String, Properties> collectMessages() {
    Map<String, Properties> answer = new HashMap<>();
    mergeAnswerMessages(answer, readMessages());
    for (MessageProvider provider : messageProviders) {
        mergeAnswerMessages(answer, provider.provide());
    }
    return answer;
}

Khi bạn sử dụng một key và một ngôn ngữ, EzyHTTP sẽ lục tìm trong map cho bạn, nếu không thấy thì nó sẽ lấy trong các message mặc định, nếu không thấy nữa thì nó sẽ chuyển đổi message từ key cho bạn.

Tổng kết

Quản lý đa ngôn ngữ thông qua các tập tin messages.properties đối với các nhà phát triển tương đối đơn giản. Tuy nhiên ẩn sau đó là các kỹ thuật phức tạp để tổ chức và lấy ra được đúng giá trị của key mà bạn truyền vào. Mình sẽ dành các bài khác để nói về cách sử dụng và các chi tiết sâu xa hơn trong việc quản lý đa ngôn ngữ trong EzyPlatform nhé.