From dc197baa70413b64b25eb07985e489e1104e4c67 Mon Sep 17 00:00:00 2001 From: Juraj Vajda Date: Fri, 28 Oct 2022 11:51:49 -0400 Subject: [PATCH] add translations --- .gitignore | 1 + api.lua | 38 ++-- i18n.py | 476 ++++++++++++++++++++++++++++++++++++++++ items.lua | 38 ++-- locale/template.txt | 21 ++ locale/x_bows.sk.tr | 21 ++ nodes.lua | 5 +- types/minetest.type.lua | 2 + 8 files changed, 565 insertions(+), 37 deletions(-) create mode 100755 i18n.py create mode 100644 locale/template.txt create mode 100644 locale/x_bows.sk.tr diff --git a/.gitignore b/.gitignore index 073f0c0..d22ff94 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ docs/build *.blend1 *.blend2 +*.old diff --git a/api.lua b/api.lua index cffc2ba..ae47f68 100644 --- a/api.lua +++ b/api.lua @@ -1,3 +1,5 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + sfinv = sfinv--[[@as Sfinv]] ---Check if table contains value @@ -180,20 +182,20 @@ function XBows.register_bow(self, name, def, override) def.custom.gravity = def.custom.gravity or -10 if def.custom.crit_chance then - def.description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Critical Arrow Chance: ' + def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Critical Arrow Chance') ..': ' .. (1 / def.custom.crit_chance) * 100 .. '%') end - def.description = def.description .. '\n' .. minetest.colorize('#00BFFF', 'Strength: ' + def.description = def.description .. '\n' .. minetest.colorize('#00BFFF', S('Strength') .. ': ' .. def.custom.strength) if def.custom.allowed_ammunition then local allowed_amm_desc = table.concat(def.custom.allowed_ammunition, '\n') if allowed_amm_desc ~= '' then - def.description = def.description .. '\nAllowed ammunition:\n' .. allowed_amm_desc + def.description = def.description .. '\n' .. S('Allowed ammunition') .. ':\n' .. allowed_amm_desc else - def.description = def.description .. '\nAllowed ammunition: none' + def.description = def.description .. '\n' .. S('Allowed ammunition') .. ': ' .. S('none') end end @@ -301,8 +303,8 @@ function XBows.register_arrow(self, name, def) max_drop_level = 0, damage_groups = {fleshy=2} } - def.custom.description_abilities = minetest.colorize('#00FF00', 'Damage: ' - .. def.custom.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', 'Charge Time: ' + def.custom.description_abilities = minetest.colorize('#00FF00', S('Damage') .. ': ' + .. def.custom.tool_capabilities.damage_groups.fleshy) .. '\n' .. minetest.colorize('#00BFFF', S('Charge Time') .. ': ' .. def.custom.tool_capabilities.full_punch_interval .. 's') def.groups = mergeTables({arrow = 1, flammable = 1}, def.groups or {}) def.custom.particle_effect = def.custom.particle_effect or 'arrow' @@ -359,13 +361,13 @@ function XBows.register_quiver(self, name, def) def.custom.groups_charged = mergeTables({quiver = 1, quiver_open = 1, flammable = 1, not_in_creative_inventory = 1}, def.groups or {}) if def.custom.faster_arrows then - def.description = def.description .. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.custom.faster_arrows) * 100 .. '%') - def.short_description = def.short_description .. '\n' .. minetest.colorize('#00FF00', 'Faster Arrows: ' .. (1 / def.custom.faster_arrows) * 100 .. '%') + def.description = def.description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') .. ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%') + def.short_description = def.short_description .. '\n' .. minetest.colorize('#00FF00', S('Faster Arrows') .. ': ' .. (1 / def.custom.faster_arrows) * 100 .. '%') end if def.custom.add_damage then - def.description = def.description .. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.custom.add_damage) - def.short_description = def.short_description .. '\n' .. minetest.colorize('#FF8080', 'Arrow Damage: +' .. def.custom.add_damage) + def.description = def.description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') .. ': +' .. def.custom.add_damage) + def.short_description = def.short_description .. '\n' .. minetest.colorize('#FF8080', S('Arrow Damage') .. ': +' .. def.custom.add_damage) end self.registered_quivers[def.custom.name] = def @@ -1648,7 +1650,7 @@ function XBowsQuiver.udate_or_create_hud(self, player, inv_list, idx) if is_no_ammo then item_def = { inventory_image = 'x_bows_arrow_slot.png', - short_description = 'No Ammo!' + short_description = S('No Ammo') .. '!' } end @@ -1928,7 +1930,7 @@ function XBowsQuiver.get_string_from_inv(self, inv) return { inv_string = minetest.serialize(t), - content_description = content_description == '' and '\nEmpty' or content_description + content_description = content_description == '' and '\n' .. S('Empty') or content_description } end @@ -2068,13 +2070,13 @@ function XBowsQuiver.sfinv_register_page(self) get = function(this, player, context) local formspec = { ---arrow - 'label[0,0;Arrows:]', + 'label[0,0;' .. minetest.formspec_escape(S('Arrows')) .. ':]', 'list[current_player;x_bows:arrow_inv;0,0.5;1,1;]', 'image[0,0.5;1,1;x_bows_arrow_slot.png;]', 'listring[current_player;x_bows:arrow_inv]', 'listring[current_player;main]', ---quiver - 'label[3.5,0;Quiver:]', + 'label[3.5,0;' .. minetest.formspec_escape(S('Quiver')) .. ':]', 'list[current_player;x_bows:quiver_inv;3.5,0.5;1,1;]', 'image[3.5,0.5;1,1;x_bows_quiver_slot.png]', 'listring[current_player;x_bows:quiver_inv]', @@ -2117,12 +2119,12 @@ function XBowsQuiver.i3_register_page(self) formspec = function(player, data, fs) local formspec = { ---arrow - 'label[0.5,1;Arrows:]', + 'label[0.5,1;' .. minetest.formspec_escape(S('Arrows')) .. ':]', 'list[current_player;x_bows:arrow_inv;0.5,1.5;1,1;]', 'listring[current_player;x_bows:arrow_inv]', 'listring[current_player;main]', ---quiver - 'label[5,1;Quiver:]', + 'label[5,1;' .. minetest.formspec_escape(S('Quiver')) .. ':]', 'list[current_player;x_bows:quiver_inv;5,1.5;1,1;]', 'listring[current_player;x_bows:quiver_inv]', 'listring[current_player;main]', @@ -2187,13 +2189,13 @@ function XBowsQuiver.ui_register_page(self) unified_inventory.style_full.standard_inv_bg, 'listcolors[#00000000;#00000000]', ---arrow - 'label[0.5,0.5;Arrows:]', + 'label[0.5,0.5;' .. minetest.formspec_escape(S('Arrows')) .. ':]', unified_inventory.single_slot(0.4,0.9), 'list[current_player;x_bows:arrow_inv;0.5,1;1,1;]', 'listring[current_player;x_bows:arrow_inv]', 'listring[current_player;main]', ---quiver - 'label[5,0.5;Quiver:]', + 'label[5,0.5;' .. minetest.formspec_escape(S('Quiver')) .. ':]', unified_inventory.single_slot(4.9,0.9), 'list[current_player;x_bows:quiver_inv;5,1;1,1;]', 'listring[current_player;x_bows:quiver_inv]', diff --git a/i18n.py b/i18n.py new file mode 100755 index 0000000..da1c825 --- /dev/null +++ b/i18n.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer +# LGPLv2.1+ +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. + +from __future__ import print_function +import os, fnmatch, re, shutil, errno +from sys import argv as _argv +from sys import stderr as _stderr + +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False, + "break-long-lines": False, + "sort": False, + "print-source": False, + "truncate-unused": False, +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods', '-m'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file', '-O'], + "break-long-lines": ['--break-long-lines', '-b'], + "sort": ['--sort', '-s'], + "print-source": ['--print-source', '-p'], + "truncate-unused": ['--truncate-unused', '-t'], +} + +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 80 + +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["sort"])} + sort output strings alphabetically + {', '.join(options["break-long-lines"])} + add extra line breaks before and after long strings + {', '.join(options["print-source"])} + add comments denoting the source file + {', '.join(options["verbose"])} + add output information + {', '.join(options["truncate-unused"])} + delete unused strings from files +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}") + run_all_subfolders(os.path.expanduser("~/.minetest/mods")) + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) +pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.*?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + if not os.path.isfile(os.path.join(folder, "modpack.txt")): + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + if folder_name == "builtin": + return "__builtin" + else: + return folder_name + else: + return None + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = f'{modname}.{language_code}.tr' + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name, header_comments): + lOut = [f"# textdomain: {mod_name}"] + if header_comments is not None: + lOut.append(header_comments) + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + if params["sort"]: + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + if params["sort"]: + localizedStrings.sort() + if params["print-source"]: + if lOut[-1] != "": + lOut.append("") + lOut.append(source) + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None and comment != "" and not comment.startswith("# textdomain:"): + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + if not params["truncate-unused"]: + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if params["break-long-lines"] and len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2]) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua_s.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_s.findall(text): + strings.append(s) + for s in pattern_lua_fs.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_fs.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +# Returns also header comments in the third return value. +def import_tr_file(tr_file): + dOut = {} + text = None + header_comment = None + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line.startswith("###"): + if header_comment is None and not latest_comment_block is None: + # Save header comments + header_comment = latest_comment_block + # Strip textdomain line + tmp_h_c = "" + for l in header_comment.split('\n'): + if not l.startswith("# textdomain:"): + tmp_h_c += l + '\n' + header_comment = tmp_h_c + + # Reset comment block if we hit a header + latest_comment_block = None + continue + elif line.startswith("#"): + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text, header_comment) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") + + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut + +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2]) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr) + exit(1) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] + for subfolder in subfolders: + update_mod(subfolder) + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]: + update_folder(modfolder) + + +main() diff --git a/items.lua b/items.lua index 147dc14..04fb0fc 100644 --- a/items.lua +++ b/items.lua @@ -1,6 +1,8 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + XBows:register_bow('bow_wood', { - description = 'Wooden Bow', - short_description = 'Wooden Bow', + description = S('Wooden Bow'), + short_description = S('Wooden Bow'), custom = { uses = 385, crit_chance = 10, @@ -22,8 +24,8 @@ XBows:register_bow('bow_wood', { }) XBows:register_arrow('arrow_wood', { - description = 'Arrow Wood', - short_description = 'Arrow Wood', + description = S('Arrow Wood'), + short_description = S('Arrow Wood'), inventory_image = 'x_bows_arrow_wood.png', custom = { recipe = { @@ -41,8 +43,8 @@ XBows:register_arrow('arrow_wood', { }) XBows:register_arrow('arrow_stone', { - description = 'Arrow Stone', - short_description = 'Arrow Stone', + description = S('Arrow Stone'), + short_description = S('Arrow Stone'), inventory_image = 'x_bows_arrow_stone.png', custom = { recipe = { @@ -59,8 +61,8 @@ XBows:register_arrow('arrow_stone', { }) XBows:register_arrow('arrow_bronze', { - description = 'Arrow Bronze', - short_description = 'Arrow Bronze', + description = S('Arrow Bronze'), + short_description = S('Arrow Bronze'), inventory_image = 'x_bows_arrow_bronze.png', custom = { recipe = { @@ -77,8 +79,8 @@ XBows:register_arrow('arrow_bronze', { }) XBows:register_arrow('arrow_steel', { - description = 'Arrow Steel', - short_description = 'Arrow Steel', + description = S('Arrow Steel'), + short_description = S('Arrow Steel'), inventory_image = 'x_bows_arrow_steel.png', custom = { recipe = { @@ -95,8 +97,8 @@ XBows:register_arrow('arrow_steel', { }) XBows:register_arrow('arrow_mese', { - description = 'Arrow Mese', - short_description = 'Arrow Mese', + description = S('Arrow Mese'), + short_description = S('Arrow Mese'), inventory_image = 'x_bows_arrow_mese.png', custom = { recipe = { @@ -113,8 +115,8 @@ XBows:register_arrow('arrow_mese', { }) XBows:register_arrow('arrow_diamond', { - description = 'Arrow Diamond', - short_description = 'Arrow Diamond', + description = S('Arrow Diamond'), + short_description = S('Arrow Diamond'), inventory_image = 'x_bows_arrow_diamond.png', custom = { recipe = { @@ -131,11 +133,11 @@ XBows:register_arrow('arrow_diamond', { }) XBows:register_quiver('quiver', { - description = 'Quiver \n\n Empty\n', - short_description = 'Quiver', + description = S('Quiver') .. '\n\n' .. S('Empty') ..'\n', + short_description = S('Quiver'), custom = { - description = 'Quiver \n\n Empty\n', - short_description = 'Quiver', + description = S('Quiver') .. '\n\n' .. S('Empty') ..'\n', + short_description = S('Quiver'), recipe = { {'group:arrow', 'group:arrow', 'group:arrow'}, {'group:arrow', 'wool:brown', 'group:arrow'}, diff --git a/locale/template.txt b/locale/template.txt new file mode 100644 index 0000000..109876a --- /dev/null +++ b/locale/template.txt @@ -0,0 +1,21 @@ +# textdomain: x_bows +Critical Arrow Chance= +Strength= +Allowed ammunition= +none= +Damage= +Charge Time= +Faster Arrows= +Arrow Damage= +No Ammo= +Arrows= +Quiver= +Empty= +Wooden Bow= +Arrow Wood= +Arrow Stone= +Arrow Bronze= +Arrow Steel= +Arrow Mese= +Arrow Diamond= +Target= diff --git a/locale/x_bows.sk.tr b/locale/x_bows.sk.tr new file mode 100644 index 0000000..be7c233 --- /dev/null +++ b/locale/x_bows.sk.tr @@ -0,0 +1,21 @@ +# textdomain: x_bows +Critical Arrow Chance=Šanca kritického šípu +Strength=Sila +Allowed ammunition=Povolené strelivo +none=Žiaden +Damage=Poškodenie +Charge Time=Doba nabíjania +Faster Arrows=Rýchlejšie šípy +Arrow Damage=Poškodenie šípom +No Ammo=Žiadne strelivo +Arrows=Šípy +Quiver=Púzdro +Empty=Prázdne +Wooden Bow=Drevený luk +Arrow Wood=Drevený šíp +Arrow Stone=Kamenný šíp +Arrow Bronze=Bronzový šíp +Arrow Steel=Oceľový šíp +Arrow Mese=Mese šíp +Arrow Diamond=Diamantový šíp +Target=Terč diff --git a/nodes.lua b/nodes.lua index 3ab9071..e5cac46 100644 --- a/nodes.lua +++ b/nodes.lua @@ -1,5 +1,8 @@ +local S = minetest.get_translator(minetest.get_current_modname()) + minetest.register_node('x_bows:target', { - description = 'Target', + description = S('Target'), + short_description = S('Target'), tiles = {'x_bows_target.png'}, is_ground_content = false, groups = {snappy=3, flammable=4, fall_damage_add_percent = -30}, diff --git a/types/minetest.type.lua b/types/minetest.type.lua index 7ce93f2..e193ca1 100644 --- a/types/minetest.type.lua +++ b/types/minetest.type.lua @@ -73,6 +73,8 @@ ---@field register_allow_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): number): nil Determines how much of a stack may be taken, put or moved to a player inventory. `player` (type `ObjectRef`) is the player who modified the inventory, `inventory` (type `InvRef`). List of possible `action` (string) values and their `inventory_info` (table) contents: `move`: `{from_list=string, to_list=string, from_index=number, to_index=number, count=number}`, `put`: `{listname=string, index=number, stack=ItemStack}`, `take`: Same as `put`. Return a numeric value to limit the amount of items to be taken, put or moved. A value of `-1` for `take` will make the source stack infinite. ---@field register_on_player_inventory_action fun(func: fun(player: ObjectRef, action: string, inventory: InvRef, inventory_info: {["from_list"]: string, ["to_list"]: string, ["from_index"]: number, ["to_index"]: number, ["count"]: number} | {["listname"]: string, ["index"]: number, ["stack"]: ItemStack}): nil): nil Called after a take, put or move event from/to/in a player inventory. Function arguments: see `minetest.register_allow_player_inventory_action`. Does not accept or handle any return value. ---@field formspec_escape fun(str: string): string returns a string, escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs. +---@field get_translator fun(textdomain: string): any +---@field get_current_modname fun(): string returns the currently loading mod's name, when loading a mod. ---Minetest settings ---@class MinetestSettings