lspsaga.nvim icon indicating copy to clipboard operation
lspsaga.nvim copied to clipboard

Internationalization

Open AndreM222 opened this issue 1 year ago • 2 comments

First of all great job on developing this plugin.

Feauture

I will like for their to either be a way for me to change the name of titles like references, code actions, etc. so I can add my translations.

Or if this plugin had translations in it it will be great.

Reason

I am currently working on adding internationalization in my own setup , most so I can practice Japanese and not bother other people who might want it in english.

My Approach

I am getting the texts from plugins to exchange them with text that will be translated depending the vim language set.

To get the language set I am using a variable build in which Is

vim.v.lang

Once I get the language it changes the values depending on the languages available and translations. For me I used a function that when text goes inside It tries to look for an available translation. if it doesnt have then it returns default string.

-- Current Language
local lang = string.sub(vim.v.lang, 1, 2)
-- Translations
local file = io.open(vim.fn.stdpath('config').."/lua/andrem222/lang/"..lang..".json", "rb")
-- Decoded translations
local jsonFile

-- Check if translation available to decode
if file then
    jsonFile = vim.json.decode(file:read("*a"))

    file:close()
end

--- This function returns the translation if available of text in the parameter
--- @param description string Description to translate
--- @return any
function Msgstr(description)
    if not jsonFile then return description end
    if not jsonFile[description] or jsonFile[description]["msgstr"] == '' then return description end

    return jsonFile[description]["msgstr"]
end

NVim Approach

They instead used a cmakelist to get translations and exchange them using gettext. This one might be better but I am still not knowledgable about that and I'm trying to learn it now to see how to make it work.

https://github.com/neovim/neovim/blob/master/src/nvim/po/CMakeLists.txt

AndreM222 avatar Nov 17 '24 18:11 AndreM222

got new way of handling

To load translation I developed this:

-- Current Language
local lang = string.sub(vim.v.lang, 1, 2)
local po_path = vim.fn.stdpath('config') .. "/lua/andrem222/po/" .. lang .. ".po"

-- Translation table
local translatedTable = {}

-- Basic PO file parser
local function parse_po(path)
    local file = io.open(path, "r")
    if not file then return {} end

    local translations = {}
    local mode = nil
    local current_msgid = {}
    local current_msgstr = {}

    local function unescape(str)
        return str:gsub('\\"', '"'):gsub("\\n", "\n")
    end

    for line in file:lines() do
        if line:match('^msgid%s+"') then
            mode = "msgid"
            current_msgid = { line:match('^msgid%s+"(.*)"') }
            current_msgstr = {}
        elseif line:match('^msgstr%s+"') then
            mode = "msgstr"
            current_msgstr = { line:match('^msgstr%s+"(.*)"') }
        elseif line:match('^"') then
            local str = line:match('^"(.*)"')
            if mode == "msgid" then
                table.insert(current_msgid, str)
            elseif mode == "msgstr" then
                table.insert(current_msgstr, str)
            end
        elseif line == "" then
            -- End of one entry
            if #current_msgid > 0 and #current_msgstr > 0 then
                local msgid_text = unescape(table.concat(current_msgid))
                local msgstr_text = unescape(table.concat(current_msgstr))
                translations[msgid_text] = msgstr_text
            end
            mode = nil
            current_msgid = {}
            current_msgstr = {}
        end
    end

    -- Handle the last entry if file doesn't end with blank line
    if #current_msgid > 0 and #current_msgstr > 0 then
        local msgid_text = unescape(table.concat(current_msgid))
        local msgstr_text = unescape(table.concat(current_msgstr))
        translations[msgid_text] = msgstr_text
    end

    file:close()
    return translations
end

-- Load translations
translatedTable = parse_po(po_path)

--- This function returns the translation if available and not empty
--- @param description string Description to translate
--- @param values string[]? Optional list of variables
--- @return string
function Msgstr(description, values)
    local translated = translatedTable[description]
    if not translated or translated == "" then
        translated = description
    end

    if values then
        local unpack = table.unpack or unpack

        local ok, formatted = pcall(string.format, translated, unpack(values))
        if ok then
            translated = formatted
        end
    end

    return translated
end

But to make all the PO's I made this cmake code

cmake_minimum_required(VERSION 3.31.6)
project(Internationalization)

set(NVIM_DIR "${CMAKE_SOURCE_DIR}/../../..")
set(TRANSLATION_DIR "${CMAKE_SOURCE_DIR}")
set(TEMPLATE_FILE "${TRANSLATION_DIR}/template.pot")
set(TRANSLATION_SCRIPT_TYPE "po")

set(LANGUAGES en ja)

file(MAKE_DIRECTORY ${TRANSLATION_DIR})
file(GLOB_RECURSE LUA_FILES "${NVIM_DIR}/*.lua")

add_custom_command(
    OUTPUT ${TEMPLATE_FILE}
    DEPENDS ${LUA_FILES}
    COMMAND bash -c "mkdir -p '${TRANSLATION_DIR}' && cd '${NVIM_DIR}' && xgettext -L Lua --keyword=Msgstr --directory=. -o '${TEMPLATE_FILE}' $(find . -name '*.lua')"
    COMMENT "Generating template.pot from Lua source"
    VERBATIM
)

foreach(LANGUAGE ${LANGUAGES})
    set(PO_FILE "${TRANSLATION_DIR}/${LANGUAGE}.${TRANSLATION_SCRIPT_TYPE}")

    add_custom_command(
        OUTPUT ${PO_FILE}
        DEPENDS ${TEMPLATE_FILE}
        COMMAND bash -c "if [ -f '${PO_FILE}' ]; then msgmerge --update --backup=off '${PO_FILE}' '${TEMPLATE_FILE}'; else cp '${TEMPLATE_FILE}' '${PO_FILE}' && sed -i '' -e 's/charset=CHARSET/charset=UTF-8/' -e 's/^\\\"Language:.*\\\"$/\\\"Language: ${LANGUAGE}\\\\n\\\"/' '${PO_FILE}'; fi"
        COMMENT "Merging or creating ${LANGUAGE}.po"
        VERBATIM
    )

    add_custom_target(${LANGUAGE}_po ALL DEPENDS ${PO_FILE})
endforeach()

add_custom_target(update_translations ALL
    DEPENDS ${TEMPLATE_FILE}
)

If you are interested I will like to implement this here. Not everyone who uses neovim is proficient in english, and I want to make it possible for everyone to enjoy the popular plugins in their own language, including this one. I know english and spanish, and I know some level of japanese to translate a good amount of stuff.

AndreM222 avatar Jun 23 '25 13:06 AndreM222

Image

This is a preview of it working. the lualine and telescope I managed to grab their texts to replace them with what I wanted.

For example the telescope one looks like this.

                    live_grep = {
                        prompt_title = Msgstr("Live Grep"),
                        results_title = Msgstr("Results"),
                        preview_title = Msgstr("Grep Preview")
                    },

it made two PO

Image

which the japanese one looks as follows,

#: lua/andrem222/plugins/tools.lua:170 lua/andrem222/plugins/tools.lua:176
#: lua/andrem222/plugins/tools.lua:182 lua/andrem222/plugins/tools.lua:188
#: lua/andrem222/plugins/tools.lua:194 lua/andrem222/plugins/tools.lua:199
#: lua/andrem222/plugins/tools.lua:205
msgid "Results"
msgstr "結果"

#: lua/andrem222/plugins/tools.lua:171 lua/andrem222/plugins/tools.lua:177
#: lua/andrem222/plugins/tools.lua:183 lua/andrem222/plugins/tools.lua:200
#: lua/andrem222/plugins/tools.lua:206
msgid "Grep Preview"
msgstr "グレッププレビュー"

#: lua/andrem222/plugins/tools.lua:175
msgid "Buffers"
msgstr "ブッファ"

#: lua/andrem222/plugins/tools.lua:181
msgid "Workspace Diagnostics"
msgstr "ワークスペース診断"

#: lua/andrem222/plugins/tools.lua:187 lua/andrem222/plugins/ui.lua:307
msgid "Help"
msgstr "ヘルプ"

#: lua/andrem222/plugins/tools.lua:189
msgid "Help Preview"
msgstr "ヘルププレビュー"

#: lua/andrem222/plugins/tools.lua:193 lua/andrem222/plugins/ui.lua:180
msgid "Keymaps"
msgstr "キーマップ"

#: lua/andrem222/plugins/tools.lua:198
msgid "Live Grep"
msgstr "ライブGrep"

AndreM222 avatar Jun 23 '25 13:06 AndreM222