Major update and bugfixes

Changelog:
This commit is contained in:
f1den committed 2025-08-01 22:27:07 +03:00
1 parent e1ed811084
commit 3686b20f23
8 files changed
+877 -369

No files matched your search

+6 -1
View File
@@ -164,5 +164,10 @@ black.py
test.py test.py
dsk/cookies.json dsk/cookies.json
# Development and testing this app
/templates/diff copy.html /templates/diff copy.html
/mods /mods
/backups
/backups_debug
new_keys.bak
newKeys_ru_ru copy.json
+77 -3
View File
@@ -15,7 +15,7 @@ app = Flask(__name__)
jsonUtils = Utils() jsonUtils = Utils()
translationHelper = TranslationHelper(deepseekApiToken=os.getenv("DEEPSEEK_API_TOKEN"), yandexApiType="ios") translationHelper = TranslationHelper(deepseekApiToken=os.getenv("DEEPSEEK_API_TOKEN"), yandexApiType="ios")
TRANSLATION_PROMPT = """You are a master Russian translator specializing in creative adaptations. OLD_TRANSLATION_PROMPT = """You are a master Russian translator specializing in creative adaptations.
For each English phrase, provide exactly 10 Russian translations showing different approaches: For each English phrase, provide exactly 10 Russian translations showing different approaches:
1. Literal translation 1. Literal translation
@@ -35,6 +35,34 @@ Format strictly as:
... ...
10. Translation 10 10. Translation 10
Never include explanations or additional text.
"""
NEW_TRANSLATION_PROMPT = """You are a master Russian translator specializing in creative adaptations.
For each English phrase, provide exactly 10 Russian translations showing different approaches.
First must contain little Easter egg based on russian films, games, etc.. but others - no.
Format strictly as:
1. Translation 1
2. Translation 2
...
10. Translation 10
Never include explanations or additional text.
"""
LEARNING_TRANSLATION_PROMPT = """You are a master Russian translator specializing in creative adaptations for Minecraft mods. Currently you are translating Oritech mod for Minecraft 1.21.1.
I pasted json with my translations before this message for u to see how translation is already done and what words you should use. If some line in this json contains "//TODO" or "//REDO" or is a whitespace ignore it.
For each English phrase, provide exactly 10 Russian translations showing different approaches.
If you are asked to translate a phrase that is already translated in json i provided, try to make something more simmilar to it and save different accent phrases from it also include one exact same translation as in the json file.
One of translations must be little Easter egg based on russian films, games, etc... Second translation must be in Humorous twist (if appropriate) approach.
Also you must strictly use already translated name for items or blocks if they appear in phrase that i am asking you to translate. Double check conjugation and declension in russian words!
If English phrase is whitespace or empty string ignore it and return one translation: "[Failed] Check text you sent to me.".
Format strictly as:
1. Translation 1
2. Translation 2
...
10. Translation 10
Never include explanations or additional text. Never include explanations or additional text.
""" """
@@ -61,7 +89,28 @@ def get_ai_suggestions():
return jsonify({'error': 'No text provided'}), 400 return jsonify({'error': 'No text provided'}), 400
try: try:
suggestions = translationHelper.getDeepseekTranslation(text, prompt=TRANSLATION_PROMPT) suggestions = translationHelper.getDeepseekTranslation(text, prompt=NEW_TRANSLATION_PROMPT)
return jsonify({
'suggestions': suggestions
})
except Exception as e:
return jsonify({
'error': str(e),
'suggestions': []
})
@app.route('/get_learning_ai_suggestions', methods=['POST', "GET"])
def get_learning_ai_suggestions():
text = request.json.get('text', '')
if not text:
return jsonify({'error': 'No text provided'}), 400
try:
translations = jsonUtils.get_existing_translations()
new_prompt = str(translations) + "\n" + LEARNING_TRANSLATION_PROMPT
# print(new_prompt)
suggestions = translationHelper.getDeepseekTranslation(text, prompt=new_prompt)
return jsonify({ return jsonify({
'suggestions': suggestions 'suggestions': suggestions
@@ -115,6 +164,12 @@ def show_differences():
'common': comparison['key_diff']['common'] 'common': comparison['key_diff']['common']
} }
# Filter TODO/REDO items
todo_items = []
for item in key_differences['added']:
if isinstance(item['value'], str) and ('//TODO' in item['value'].upper() or '//REDO' in item['value'].upper()):
todo_items.append(item)
# Load existing translations (both original and new, already sorted) # Load existing translations (both original and new, already sorted)
existing_translations = jsonUtils.get_existing_translations() existing_translations = jsonUtils.get_existing_translations()
@@ -123,7 +178,8 @@ def show_differences():
key_differences=key_differences, key_differences=key_differences,
ru_file=ru_file, ru_file=ru_file,
en_file=en_file, en_file=en_file,
existing_translations=existing_translations) existing_translations=existing_translations,
todo_items=todo_items)
except Exception as e: except Exception as e:
return render_template('error.html', error=str(e)) return render_template('error.html', error=str(e))
@@ -141,5 +197,23 @@ def save_translations():
except Exception as e: except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/save_todo_translations', methods=['POST'])
def save_todo_translations():
translations = request.json
if not translations:
return jsonify({'status': 'error', 'message': 'No translations provided'}), 400
try:
# Load existing translations
existing_translations = jsonUtils.load_json('newKeys_ru_ru.json')
# Update with new translations
existing_translations.update(translations)
# Save back to file
jsonUtils.save_json('newKeys_ru_ru.json', existing_translations)
return jsonify({'status': 'success', 'saved': len(translations)})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
if __name__ == '__main__': if __name__ == '__main__':
app.run() app.run()
+344 -332
View File
@@ -1,11 +1,11 @@
{ {
"advancements.oritech.augmenter": "Кибернетика!", "advancements.oritech.augmenter": "Киберимпланты!",
"advancements.oritech.augmenter.description": "Преодолейте свои естественные… лимиты! Предупреждение: Может вызвать повышенное желание говорить «бип-буп».", "advancements.oritech.augmenter.description": "Преодолейте свои естественные… лимиты! Внимание: Может вызвать повышенное желание взорвать Арасака-тауэр.",
"advancements.oritech.reactor": "Ядерная энергетика", "advancements.oritech.reactor": "Ядерная энергетика",
"advancements.oritech.reactor.description": "Собери реактор. Безопасность? Не, не слышал... Ой, смотри, как красиво светится!", "advancements.oritech.reactor.description": "Собери реактор. Безопасность? Не, не слышал... Ой, смотри, как красиво светится!",
"block.oritech.advanced_augment_station": [ "block.oritech.advanced_augment_station": [
{ {
"text": "Квантовая исследовательская станция", "text": "Станция квантовых исследований",
"color": "#40f5ef" "color": "#40f5ef"
} }
], ],
@@ -20,336 +20,348 @@
"block.oritech.carbon_plating_pressure_plate": "Нажимная плита из усиленной карбоном обшивки", "block.oritech.carbon_plating_pressure_plate": "Нажимная плита из усиленной карбоном обшивки",
"block.oritech.carbon_plating_slab": "Плита из усиленной карбоном обшивки", "block.oritech.carbon_plating_slab": "Плита из усиленной карбоном обшивки",
"block.oritech.carbon_plating_stairs": "Ступеньки из усиленной карбоном обшивки", "block.oritech.carbon_plating_stairs": "Ступеньки из усиленной карбоном обшивки",
"block.oritech.machine_core_hidden": "", "block.oritech.machine_core_hidden": "Цельная обшивка",
"block.oritech.machine_silk_touch_addon": "", "block.oritech.machine_silk_touch_addon": "Модуль: Шелковое касание",
"block.oritech.refinery_block": "", "block.oritech.refinery_block": "Очистительный завод",
"block.oritech.refinery_module_block": "", "block.oritech.refinery_module_block": "Модуль: Перегонный куб",
"block.oritech.simple_augment_station": "", "block.oritech.simple_augment_station": "Станция изучения кибернетики",
"block.oritech.still_biofuel_block": "", "block.oritech.still_biofuel_block": "Биотопливо",
"block.oritech.still_molten_adamant_block": "", "block.oritech.still_molten_adamant_block": "Расплавленный адамантит",
"block.oritech.still_molten_biosteel_block": "", "block.oritech.still_molten_biosteel_block": "Расплавленная биосталь",
"block.oritech.still_molten_duratium_block": "", "block.oritech.still_molten_duratium_block": "Расплавленный дюратий",
"block.oritech.still_molten_energite_block": "", "block.oritech.still_molten_energite_block": "Расплавленный энергит",
"block.oritech.still_molten_fluxite": "", "block.oritech.still_molten_fluxite": "Расплавленный флюксит",
"block.oritech.transparent_item_pipe": "", "block.oritech.transparent_item_pipe": "Прозрачная предметная труба",
"block.oritech.unstable_container": "", "block.oritech.unstable_container": "Сейф Шредингера",
"emi.category.oritech.refinery": "", "emi.category.oritech.refinery": "Очистка",
"fluid.oritech.still_biofuel": "", "fluid.oritech.still_biofuel": "Биотопливо",
"fluid.oritech.still_diesel": "", "fluid.oritech.still_diesel": "Дизель",
"fluid.oritech.still_heavy_oil": "", "fluid.oritech.still_heavy_oil": "Тяжелая нефть",
"fluid.oritech.still_mineral_slurry": "", "fluid.oritech.still_mineral_slurry": "Минеральный шлак",
"fluid.oritech.still_molten_adamant": "", "fluid.oritech.still_molten_adamant": "Расплавленный адамантит",
"fluid.oritech.still_molten_biosteel": "", "fluid.oritech.still_molten_biosteel": "Расплавленная биосталь",
"fluid.oritech.still_molten_duratium": "", "fluid.oritech.still_molten_duratium": "Расплавленный дюратий",
"fluid.oritech.still_molten_energite": "", "fluid.oritech.still_molten_energite": "Расплавленный энергит",
"fluid.oritech.still_molten_fluxite": "", "fluid.oritech.still_molten_fluxite": "Расплавленный флюксит",
"fluid.oritech.still_naphtha": "", "fluid.oritech.still_naphtha": "Лёгкая нафта",
"fluid.oritech.still_sheol_fire": "", "fluid.oritech.still_sheol_fire": "Шеольский огонь",
"fluid.oritech.still_silicon_wash": "", "fluid.oritech.still_silicon_wash": "Кремниевый раствор",
"fluid.oritech.still_strange_matter": "", "fluid.oritech.still_strange_matter": "Странная материя",
"fluid.oritech.still_sulfuric_acid": "", "fluid.oritech.still_sulfuric_acid": "Серная кислота",
"item.oritech.clay_catalyst_beads": "", "item.oritech.clay_catalyst_beads": "Катализатор из гранулированной глины",
"item.oritech.electric_mace": "", "item.oritech.electric_mace": "Электрическая булава",
"item.oritech.ion_thruster": "", "item.oritech.ion_thruster": "Ионный двигатель",
"item.oritech.portable_laser": "", "item.oritech.portable_laser": [
"item.oritech.reinforced_carbon_sheet": "", {
"item.oritech.still_biofuel_bucket": "", "text": "Эндеритовый рельсотрон",
"item.oritech.still_diesel_bucket": "", "color": "#ffd505"
"item.oritech.still_heavy_oil_bucket": "", }
"item.oritech.still_mineral_slurry_bucket": "", ],
"item.oritech.still_molten_adamant_bucket": "", "item.oritech.reinforced_carbon_sheet": "Углепластик",
"item.oritech.still_molten_biosteel_bucket": "", "item.oritech.still_biofuel_bucket": "Ведро биотоплива",
"item.oritech.still_molten_duratium_bucket": "", "item.oritech.still_diesel_bucket": "Ведро дизеля",
"item.oritech.still_molten_energite_bucket": "", "item.oritech.still_heavy_oil_bucket": "Ведро тяжёлой нефти",
"item.oritech.still_molten_fluxite_bucket": "", "item.oritech.still_mineral_slurry_bucket": "Ведро минерального шлака",
"item.oritech.still_naphtha_bucket": "", "item.oritech.still_molten_adamant_bucket": "Ведро расплавленного адамантита",
"item.oritech.still_sheol_fire_bucket": "", "item.oritech.still_molten_biosteel_bucket": "Ведро расплавленной биостали",
"item.oritech.still_silicon_wash_bucket": "", "item.oritech.still_molten_duratium_bucket": "Ведро расплавленного дюратия",
"item.oritech.still_strange_matter_bucket": "", "item.oritech.still_molten_energite_bucket": "Ведро расплавленного энергита",
"item.oritech.still_sulfuric_acid_bucket": "", "item.oritech.still_molten_fluxite_bucket": "Ведро расплавленного флюксита",
"item.oritech.unstable_container": "", "item.oritech.still_naphtha_bucket": "Ведро лёгкой нафты",
"key.oritech.augment_screen": "", "item.oritech.still_sheol_fire_bucket": "Ведро шеольского пламени",
"message.oritech.steamengine.controller_link": "", "item.oritech.still_silicon_wash_bucket": "Ведро кремниевого раствора",
"oracle_index.title.oritech": "", "item.oritech.still_strange_matter_bucket": "Ведро странной материи",
"oritech.text.augment.armor": "", "item.oritech.still_sulfuric_acid_bucket": "Ведро серной кислоты",
"oritech.text.augment.armor.desc": "", "item.oritech.unstable_container": "Сейф Шредингера",
"oritech.text.augment.armor.desc.1": "", "key.oritech.augment_screen": "(Удерживайте) Элементы управления имплантатами [Oritech]//TODO",
"oritech.text.augment.attackdamage": "", "message.oritech.steamengine.controller_link": "Паровой двигатель соединен с основным двигателем (см. выделенный блок). Используйте только основной двигатель.",
"oritech.text.augment.attackdamage.desc": "фпвыфпв", "oracle_index.title.oritech": "Oritech",
"oritech.text.augment.attackdamage.desc.1": "", "oritech.text.augment.armor": "ХУЙ //TODO",
"oritech.text.augment.autofeeder": "", "oritech.text.augment.armor.desc": "//TODO",
"oritech.text.augment.autofeeder.desc": "", "oritech.text.augment.armor.desc.1": "//TODO",
"oritech.text.augment.autofeeder.desc.1": "", "oritech.text.augment.attackdamage": "//TODO",
"oritech.text.augment.autofeeder.desc.2": "", "oritech.text.augment.attackdamage.desc": "//TODO",
"oritech.text.augment.betterarmor": "", "oritech.text.augment.attackdamage.desc.1": "//TODO",
"oritech.text.augment.betterarmor.desc": "", "oritech.text.augment.autofeeder": "//TODO",
"oritech.text.augment.betterarmor.desc.1": "", "oritech.text.augment.autofeeder.desc": "//TODO",
"oritech.text.augment.blockreach": "", "oritech.text.augment.autofeeder.desc.1": "//TODO",
"oritech.text.augment.blockreach.desc": "", "oritech.text.augment.autofeeder.desc.2": "//TODO",
"oritech.text.augment.blockreach.desc.1": "", "oritech.text.augment.betterarmor": "//TODO",
"oritech.text.augment.cloak": "", "oritech.text.augment.betterarmor.desc": "//TODO",
"oritech.text.augment.cloak.desc": "", "oritech.text.augment.betterarmor.desc.1": "//TODO",
"oritech.text.augment.cloak.desc.1": "", "oritech.text.augment.blockreach": "//TODO",
"oritech.text.augment.cloak.desc.3": "", "oritech.text.augment.blockreach.desc": "//TODO",
"oritech.text.augment.dwarf": "", "oritech.text.augment.blockreach.desc.1": "//TODO",
"oritech.text.augment.dwarf.desc": "", "oritech.text.augment.cloak": "Плащ",
"oritech.text.augment.dwarf.desc.1": "", "oritech.text.augment.cloak.desc": "//TODO",
"oritech.text.augment.dwarf.desc.2": "", "oritech.text.augment.cloak.desc.1": "//TODO",
"oritech.text.augment.farblockreach": "", "oritech.text.augment.cloak.desc.3": "//TODO",
"oritech.text.augment.farblockreach.desc": "", "oritech.text.augment.dwarf": "//TODO",
"oritech.text.augment.farblockreach.desc.1": "", "oritech.text.augment.dwarf.desc": "//TODO",
"oritech.text.augment.farblockreach.desc.2": "", "oritech.text.augment.dwarf.desc.1": "//TODO",
"oritech.text.augment.flight": "", "oritech.text.augment.dwarf.desc.2": "//TODO",
"oritech.text.augment.flight.desc": "", "oritech.text.augment.farblockreach": "//TODO",
"oritech.text.augment.flight.desc.1": "", "oritech.text.augment.farblockreach.desc": "//TODO",
"oritech.text.augment.giant": "", "oritech.text.augment.farblockreach.desc.1": "//TODO",
"oritech.text.augment.giant.desc": "", "oritech.text.augment.farblockreach.desc.2": "//TODO",
"oritech.text.augment.giant.desc.1": "", "oritech.text.augment.flight": "Полёт",
"oritech.text.augment.giant.desc.2": "", "oritech.text.augment.flight.desc": "//TODO",
"oritech.text.augment.gravity": "", "oritech.text.augment.flight.desc.1": "//TODO",
"oritech.text.augment.gravity.desc": "", "oritech.text.augment.giant": "Гигантизм",
"oritech.text.augment.gravity.desc.1": "", "oritech.text.augment.giant.desc": "//TODO",
"oritech.text.augment.hpboost": "", "oritech.text.augment.giant.desc.1": "//TODO",
"oritech.text.augment.hpboost.desc": "", "oritech.text.augment.giant.desc.2": "//TODO",
"oritech.text.augment.hpboost.desc.1": "", "oritech.text.augment.gravity": "//TODO",
"oritech.text.augment.hpboostmore": "", "oritech.text.augment.gravity.desc": "//TODO",
"oritech.text.augment.hpboostmore.desc": "", "oritech.text.augment.gravity.desc.1": "//TODO",
"oritech.text.augment.hpboostmore.desc.1": "", "oritech.text.augment.hpboost": "//TODO",
"oritech.text.augment.hpboostultimate": "", "oritech.text.augment.hpboost.desc": "//TODO",
"oritech.text.augment.hpboostultimate.desc": "", "oritech.text.augment.hpboost.desc.1": "//TODO",
"oritech.text.augment.hpboostultimate.desc.1": "", "oritech.text.augment.hpboostmore": "//TODO",
"oritech.text.augment.hpboostultra": "", "oritech.text.augment.hpboostmore.desc": "//TODO",
"oritech.text.augment.hpboostultra.desc": "фыа", "oritech.text.augment.hpboostmore.desc.1": "//TODO",
"oritech.text.augment.hpboostultra.desc.1": "", "oritech.text.augment.hpboostultimate": "//TODO",
"oritech.text.augment.luck": "", "oritech.text.augment.hpboostultimate.desc": "//TODO",
"oritech.text.augment.luck.desc": "", "oritech.text.augment.hpboostultimate.desc.1": "//TODO",
"oritech.text.augment.luck.desc.1": "", "oritech.text.augment.hpboostultra": "//TODO",
"oritech.text.augment.luck.desc.2": "", "oritech.text.augment.hpboostultra.desc": "//TODO",
"oritech.text.augment.magnet": "", "oritech.text.augment.hpboostultra.desc.1": "//TODO",
"oritech.text.augment.magnet.desc": "", "oritech.text.augment.luck": "Удача",
"oritech.text.augment.magnet.desc.1": "", "oritech.text.augment.luck.desc": "//TODO",
"oritech.text.augment.magnet.desc.2": "", "oritech.text.augment.luck.desc.1": "//TODO",
"oritech.text.augment.miningspeed": "", "oritech.text.augment.luck.desc.2": "//TODO",
"oritech.text.augment.miningspeed.desc": "", "oritech.text.augment.magnet": "Магнит",
"oritech.text.augment.miningspeed.desc.1": "", "oritech.text.augment.magnet.desc": "//TODO",
"oritech.text.augment.nightvision": "", "oritech.text.augment.magnet.desc.1": "//TODO",
"oritech.text.augment.nightvision.desc": "", "oritech.text.augment.magnet.desc.2": "//TODO",
"oritech.text.augment.nightvision.desc.1": "", "oritech.text.augment.miningspeed": "//TODO",
"oritech.text.augment.nightvision.desc.2": "", "oritech.text.augment.miningspeed.desc": "//TODO",
"oritech.text.augment.orefinder": "", "oritech.text.augment.miningspeed.desc.1": "//TODO",
"oritech.text.augment.orefinder.desc": "", "oritech.text.augment.nightvision": "Ночное зрение",
"oritech.text.augment.orefinder.desc.1": "", "oritech.text.augment.nightvision.desc": "//TODO",
"oritech.text.augment.orefinder.desc.2": "", "oritech.text.augment.nightvision.desc.1": "//TODO",
"oritech.text.augment.portal": "", "oritech.text.augment.nightvision.desc.2": "//TODO",
"oritech.text.augment.portal.desc": "", "oritech.text.augment.orefinder": "//TODO",
"oritech.text.augment.portal.desc.1": "", "oritech.text.augment.orefinder.desc": "//TODO",
"oritech.text.augment.speedboost": "", "oritech.text.augment.orefinder.desc.1": "//TODO",
"oritech.text.augment.speedboost.desc": "", "oritech.text.augment.orefinder.desc.2": "//TODO",
"oritech.text.augment.speedboost.desc.1": "", "oritech.text.augment.portal": "//TODO",
"oritech.text.augment.stepassist": "", "oritech.text.augment.portal.desc": "//TODO",
"oritech.text.augment.stepassist.desc": "", "oritech.text.augment.portal.desc.1": "//TODO",
"oritech.text.augment.stepassist.desc.1": "", "oritech.text.augment.speedboost": "//TODO",
"oritech.text.augment.stepassist.desc.2": "", "oritech.text.augment.speedboost.desc": "//TODO",
"oritech.text.augment.superattackdamage": "", "oritech.text.augment.speedboost.desc.1": "//TODO",
"oritech.text.augment.superattackdamage.desc": "", "oritech.text.augment.stepassist": "//TODO",
"oritech.text.augment.superattackdamage.desc.1": "фвып", "oritech.text.augment.stepassist.desc": "//TODO",
"oritech.text.augment.superminingspeed": "", "oritech.text.augment.stepassist.desc.1": "//TODO",
"oritech.text.augment.superminingspeed.desc": "", "oritech.text.augment.stepassist.desc.2": "//TODO",
"oritech.text.augment.superminingspeed.desc.1": "", "oritech.text.augment.superattackdamage": "//TODO",
"oritech.text.augment.superminingspeed.desc.2": "", "oritech.text.augment.superattackdamage.desc": "//TODO",
"oritech.text.augment.superspeedboost": "", "oritech.text.augment.superattackdamage.desc.1": "//TODO",
"oritech.text.augment.superspeedboost.desc": "", "oritech.text.augment.superminingspeed": "//TODO",
"oritech.text.augment.superspeedboost.desc.1": "", "oritech.text.augment.superminingspeed.desc": "//TODO",
"oritech.text.augment.superspeedboost.desc.2": "", "oritech.text.augment.superminingspeed.desc.1": "//TODO",
"oritech.text.augment.ultimatearmor": "", "oritech.text.augment.superminingspeed.desc.2": "//TODO",
"oritech.text.augment.ultimatearmor.desc": "", "oritech.text.augment.superspeedboost": "//TODO",
"oritech.text.augment.ultimatearmor.desc.1": "", "oritech.text.augment.superspeedboost.desc": "//TODO",
"oritech.text.augment.waterbreath": "", "oritech.text.augment.superspeedboost.desc.1": "//TODO",
"oritech.text.augment.waterbreath.desc": "", "oritech.text.augment.superspeedboost.desc.2": "//TODO",
"oritech.text.augment.waterbreath.desc.1": "", "oritech.text.augment.ultimatearmor": "//TODO",
"oritech.text.augment.weaponreach": "", "oritech.text.augment.ultimatearmor.desc": "//TODO",
"oritech.text.augment.weaponreach.desc": "", "oritech.text.augment.ultimatearmor.desc.1": "//TODO",
"oritech.text.augment.weaponreach.desc.1": "", "oritech.text.augment.waterbreath": "//TODO",
"oritech.text.augment_ingredient_tip": "", "oritech.text.augment.waterbreath.desc": "//TODO",
"oritech.text.augment_op.apply": "", "oritech.text.augment.waterbreath.desc.1": "//TODO",
"oritech.text.augment_op.pending": "", "oritech.text.augment.weaponreach": "//TODO",
"oritech.text.augment_op.remove": "", "oritech.text.augment.weaponreach.desc": "//TODO",
"oritech.text.augment_op.research": "", "oritech.text.augment.weaponreach.desc.1": "//TODO",
"oritech.text.augment_research_time": "", "oritech.text.augment_ingredient_tip": "//TODO",
"oritech.text.augment_resource_cost": "", "oritech.text.augment_op.apply": "Применить",
"oritech.text.augment_toggle": "", "oritech.text.augment_op.pending": "Изучение...",
"oritech.text.energy_cost": "", "oritech.text.augment_op.remove": "Удалить \\ Снять //TODO",
"oritech.text.missing_requirements_title": "", "oritech.text.augment_op.research": "Изучить \\ Изучение //TODO",
"oritech.text.required_station": "", "oritech.text.augment_research_time": "//TODO",
"rei.process.oritech:refinery": "", "oritech.text.augment_resource_cost": "//TODO",
"tag.item.c.bananas": "", "oritech.text.augment_toggle": "//TODO",
"tag.item.c.biodiesel": "", "oritech.text.energy_cost": "//TODO",
"tag.item.c.clumps": "", "oritech.text.missing_requirements_title": "//TODO",
"tag.item.c.clumps.copper": "", "oritech.text.required_station": "//TODO",
"tag.item.c.clumps.gold": "", "rei.process.oritech:refinery": "Очистка",
"tag.item.c.clumps.iron": "", "tag.item.c.bananas": "Бананы",
"tag.item.c.clumps.nickel": "", "tag.item.c.biodiesel": "Биодизель",
"tag.item.c.clumps.platinum": "", "tag.item.c.clumps": "Глыбы",
"tag.item.c.dusts.plutonium": "", "tag.item.c.clumps.copper": "Медные глыбы",
"tag.item.c.dusts.uranium": "", "tag.item.c.clumps.gold": "Золотые глыбы",
"tag.item.c.fuels.bio": "", "tag.item.c.clumps.iron": "Железные глыбы",
"tag.item.c.fuels.block.bio": "", "tag.item.c.clumps.nickel": "Никелевая пыль",
"tag.item.c.high_power_biodiesel": "", "tag.item.c.clumps.platinum": "Платиновая пыль",
"tag.item.c.oil": "", "tag.item.c.dusts.plutonium": "Плутониевая пыль",
"tag.item.c.ores.uranium": "", "tag.item.c.dusts.uranium": "Урановая пыль",
"tag.item.c.plastics": "", "tag.item.c.fuels.bio": "Био топливо",
"tag.item.c.raw_materials.nickel": "", "tag.item.c.fuels.block.bio": "Блоки биотоплива",
"tag.item.c.raw_materials.platinum": "", "tag.item.c.high_power_biodiesel": "Высокоэффективный биодизель",
"tag.item.c.raw_materials.uranium": "", "tag.item.c.oil": "Нефть",
"tag.item.c.steam": "", "tag.item.c.ores.uranium": "Урановые руды",
"tag.item.c.tools.wrench": "", "tag.item.c.plastics": "Пластик",
"tag.item.oritech.biofuel": "", "tag.item.c.raw_materials.nickel": "Необработанный никель",
"tag.item.oritech.biomatter": "", "tag.item.c.raw_materials.platinum": "Необработанную платину",
"tag.item.oritech.reactor_coolant_items": "", "tag.item.c.raw_materials.uranium": "Необработанный уран",
"tag.item.oritech.recyclable.biomass": "", "tag.item.c.steam": "Пар",
"tag.item.oritech.recyclable.copper_dust": "", "tag.item.c.tools.wrench": "Гаечные ключи",
"tag.item.oritech.recyclable.diamond": "", "tag.item.oritech.biofuel": "Биотопливо",
"tag.item.oritech.recyclable.gold_dust": "", "tag.item.oritech.biomatter": "Биоматерия",
"tag.item.oritech.recyclable.gravel": "dsag", "tag.item.oritech.reactor_coolant_items": "Хладагенты",
"tag.item.oritech.recyclable.iron_dust": "", "tag.item.oritech.recyclable.biomass": "Перерабатывается в биомассу",
"tag.item.oritech.recyclable.netherite_scrap": "", "tag.item.oritech.recyclable.copper_dust": "Перерабатывается в медную пыль",
"tag.item.oritech.recyclable.quartz_dust_2": "", "tag.item.oritech.recyclable.diamond": "Перерабатывается в алмаз",
"tag.item.oritech.recyclable.quartz_dust_4": "", "tag.item.oritech.recyclable.gold_dust": "Перерабатывается в золотую пыль",
"tag.item.oritech.recyclable.red_sand": "", "tag.item.oritech.recyclable.gravel": "Перерабатывается в гравий",
"tag.item.oritech.recyclable.redstone_dust": "", "tag.item.oritech.recyclable.iron_dust": "Перерабатывается в железную пыль",
"tag.item.oritech.recyclable.sand": "", "tag.item.oritech.recyclable.netherite_scrap": "Перерабатывается в незеритовый лом",
"tag.item.oritech.recyclable.small_copper_dust": "", "tag.item.oritech.recyclable.quartz_dust_2": "Перерабатывается в кварцевую пыль (2)",
"tag.item.oritech.recyclable.small_gold_dust": "", "tag.item.oritech.recyclable.quartz_dust_4": "Перерабатывается в кварцевую пыль (4)",
"tag.item.oritech.recyclable.small_iron_dust": "", "tag.item.oritech.recyclable.red_sand": "Перерабатывается в красный песок",
"tag.item.oritech.recyclable.string": "", "tag.item.oritech.recyclable.redstone_dust": "Перерабатывается в редстоуновую пыль",
"tag.item.oritech.turbofuel": "", "tag.item.oritech.recyclable.sand": "Перерабатывается в песок",
"text.config.oritech-config.category.addonConfig": "", "tag.item.oritech.recyclable.small_copper_dust": "Перерабатывается в маленькую кучку медной пыли",
"text.config.oritech-config.category.electricMace": "", "tag.item.oritech.recyclable.small_gold_dust": "Перерабатывается в маленькую кучку золотой пыли",
"text.config.oritech-config.category.portableLaserConfig": "", "tag.item.oritech.recyclable.small_iron_dust": "Перерабатывается в маленькую кучку железной пыли",
"text.config.oritech-config.category.processingMachines.refineryData": "", "tag.item.oritech.recyclable.string": "Перерабатывается в нить",
"text.config.oritech-config.option.acceleratorMotorRFCapacity": "", "tag.item.oritech.turbofuel": "Турботопливо",
"text.config.oritech-config.option.additiveAddons": "", "text.config.oritech-config.category.addonConfig": "Данные Модуля",
"text.config.oritech-config.option.additiveAddons.tooltip": "", "text.config.oritech-config.category.electricMace": "Электрическая булава",
"text.config.oritech-config.option.addonConfig.chamberAddonEfficiency": "", "text.config.oritech-config.category.portableLaserConfig": "Эндеритовый рельсотрон",
"text.config.oritech-config.option.addonConfig.efficiencyAddonEfficiency": "", "text.config.oritech-config.category.processingMachines.refineryData": "Настройки очистительного завода",
"text.config.oritech-config.option.addonConfig.speedAddonEfficiency": "", "text.config.oritech-config.option.acceleratorMotorRFCapacity": "Емкость RF: Линейный двигатель ускорителя",
"text.config.oritech-config.option.addonConfig.speedAddonSpeed": "", "text.config.oritech-config.option.additiveAddons": "Тип множителя модулей",
"text.config.oritech-config.option.addonConfig.ultimateAddonEfficiency": "", "text.config.oritech-config.option.additiveAddons.tooltip": "Позволяет выбрать тип множителя для модулей: сложение и перемножение.",
"text.config.oritech-config.option.addonConfig.ultimateAddonSpeed": "", "text.config.oritech-config.option.addonConfig.chamberAddonEfficiency": "//TODO",
"text.config.oritech-config.option.augmenterMaxEnergy": "", "text.config.oritech-config.option.addonConfig.efficiencyAddonEfficiency": "//TODO",
"text.config.oritech-config.option.boringNukes": "", "text.config.oritech-config.option.addonConfig.speedAddonEfficiency": "//TODO",
"text.config.oritech-config.option.catalystHyperExpFactor": "", "text.config.oritech-config.option.addonConfig.speedAddonSpeed": "//TODO",
"text.config.oritech-config.option.catalystHyperExpFactor.tooltip": "", "text.config.oritech-config.option.addonConfig.ultimateAddonEfficiency": "//TODO",
"text.config.oritech-config.option.chainsawTreeCutting": "", "text.config.oritech-config.option.addonConfig.ultimateAddonSpeed": "//TODO",
"text.config.oritech-config.option.electricMace.baseDamage": "", "text.config.oritech-config.option.augmenterMaxEnergy": "//TODO",
"text.config.oritech-config.option.electricMace.chargeSpeed": "", "text.config.oritech-config.option.boringNukes": "//TODO",
"text.config.oritech-config.option.electricMace.energyCapacity": "", "text.config.oritech-config.option.catalystHyperExpFactor": "//TODO",
"text.config.oritech-config.option.electricMace.energyUsage": "", "text.config.oritech-config.option.catalystHyperExpFactor.tooltip": "//TODO",
"text.config.oritech-config.option.electricMace.lightningCostMultiplier": "", "text.config.oritech-config.option.chainsawTreeCutting": "//TODO",
"text.config.oritech-config.option.enableHelpButton": "", "text.config.oritech-config.option.electricMace.baseDamage": "//TODO",
"text.config.oritech-config.option.generateOresFabricOnly": "", "text.config.oritech-config.option.electricMace.chargeSpeed": "//TODO",
"text.config.oritech-config.option.generateOresFabricOnly.tooltip": "", "text.config.oritech-config.option.electricMace.energyCapacity": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio": "", "text.config.oritech-config.option.electricMace.energyUsage": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio.tooltip": "", "text.config.oritech-config.option.electricMace.lightningCostMultiplier": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.steamToRfRatio": "", "text.config.oritech-config.option.enableHelpButton": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull": "", "text.config.oritech-config.option.generateOresFabricOnly": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull.tooltip": "", "text.config.oritech-config.option.generateOresFabricOnly.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull": "", "text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull.tooltip": "", "text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamId": "", "text.config.oritech-config.option.generators.steamEngineData.steamToRfRatio": "//TODO",
"text.config.oritech-config.option.generators.steamId.tooltip": "", "text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull": "//TODO",
"text.config.oritech-config.option.layeredExtenders": "", "text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull.tooltip": "//TODO",
"text.config.oritech-config.option.layeredExtenders.tooltip": "", "text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.blockBreakSpeed": "", "text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull.tooltip": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.damageBase": "", "text.config.oritech-config.option.generators.steamId": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyCapacity": "", "text.config.oritech-config.option.generators.steamId.tooltip": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyPerBoom": "", "text.config.oritech-config.option.layeredExtenders": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyPerTick": "", "text.config.oritech-config.option.layeredExtenders.tooltip": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.explosionStrength": "фывфыв", "text.config.oritech-config.option.portableLaserConfig.blockBreakSpeed": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.energyCapacity": "", "text.config.oritech-config.option.portableLaserConfig.damageBase": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.energyPerTick": "", "text.config.oritech-config.option.portableLaserConfig.energyCapacity": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyExtraction": "", "text.config.oritech-config.option.portableLaserConfig.energyPerBoom": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyInsertion": "", "text.config.oritech-config.option.portableLaserConfig.energyPerTick": "//TODO",
"text.config.oritech-config.option.reactorMaxEnergyOutput": "", "text.config.oritech-config.option.portableLaserConfig.explosionStrength": "//TODO",
"text.config.oritech-config.option.reactorMaxEnergyStored": "", "text.config.oritech-config.option.processingMachines.refineryData.energyCapacity": "//TODO",
"text.config.oritech-config.option.showMachinePreview": "", "text.config.oritech-config.option.processingMachines.refineryData.energyPerTick": "//TODO",
"text.config.oritech-config.option.unstableContainerBaseCapacity": "", "text.config.oritech-config.option.processingMachines.refineryData.maxEnergyExtraction": "//TODO",
"text.config.oritech-config.section.augments": "", "text.config.oritech-config.option.processingMachines.refineryData.maxEnergyInsertion": "//TODO",
"text.oritech.augmenter_active": "", "text.config.oritech-config.option.reactorMaxEnergyOutput": "//TODO",
"text.oritech.augmenter_creative_tooltip": "", "text.config.oritech-config.option.reactorMaxEnergyStored": "//TODO",
"text.oritech.begin_research": "", "text.config.oritech-config.option.showMachinePreview": "//TODO",
"text.oritech.cancel": "", "text.config.oritech-config.option.unstableContainerBaseCapacity": "//TODO",
"text.oritech.install": "", "text.config.oritech-config.section.augments": "//TODO",
"text.oritech.load_augments.tooltip": "", "text.oritech.augmenter_active": "Осталось: %s сек.",
"text.oritech.loaded_augments": "", "text.oritech.augmenter_creative_tooltip": "В творческом режиме вы можете исследовать / применять улучшения без необходимых ресурсов.",
"text.oritech.begin_research": "Начать изучение",
"text.oritech.cancel": "Отмена",
"text.oritech.install": "Установить",
"text.oritech.load_augments.tooltip": "Загрузить информацию о киберимплантах игрока в механизм",
"text.oritech.loaded_augments": "Загружено киберимплантов: %s",
"text.oritech.noop": "", "text.oritech.noop": "",
"text.oritech.open_inv.tooltip": "", "text.oritech.open_inv.tooltip": "Открыть центр кибернетических улучшений",
"text.oritech.remove": "", "text.oritech.remove": "Убрать",
"text.oritech.unstable_container_blocked": "", "text.oritech.unstable_container_blocked": "Невозможно поставить, область заблокирована.",
"title.oritech.energy.inAvgSecond": "", "title.oritech.energy.inAvgSecond": "∅ %s RF/t",
"title.oritech.energy.inAvgSecond.tooltip": "", "title.oritech.energy.inAvgSecond.tooltip": "Средний RF/t полученный за последнюю секунду.",
"title.oritech.energy.inLastTick": "", "title.oritech.energy.inLastTick": "📈 %s RF/t",
"title.oritech.energy.inLastTick.tooltip": "", "title.oritech.energy.inLastTick.tooltip": "Всего RF полученных за последний тик.",
"title.oritech.energy.inPeak": "", "title.oritech.energy.inPeak": "📊 %s RF/t",
"title.oritech.energy.inPeak.tooltip": "", "title.oritech.energy.inPeak.tooltip": "Пиковый RF/t полученный за последнюю секунду.",
"title.oritech.energy.inSources": "", "title.oritech.energy.inSources": "%s Источник(ов)",
"title.oritech.energy.inSources.tooltip": "", "title.oritech.energy.inSources.tooltip": "Количество событий получения RF за последний тик\n(Например, количество механизмов/блоков которые отдали энергию).",
"title.oritech.energy.outAvgSecond": "", "title.oritech.energy.outAvgSecond": "∅ %s RF/t",
"title.oritech.energy.outAvgSecond.tooltip": "", "title.oritech.energy.outAvgSecond.tooltip": "Средняя отдача RF/t за последнюю секунду.",
"title.oritech.energy.outLastTick": "", "title.oritech.energy.outLastTick": "📈 %s RF/t",
"title.oritech.energy.outLastTick.tooltip": "", "title.oritech.energy.outLastTick.tooltip": "Количество RF, которое было отдано из этого блока за последний тик.",
"title.oritech.energy.outPeak": "", "title.oritech.energy.outPeak": "📊 %s RF/t",
"title.oritech.energy.outPeak.tooltip": "", "title.oritech.energy.outPeak.tooltip": "Пиковая отдача RF/t за последнюю секунду.",
"title.oritech.item_filter.component": "", "title.oritech.item_filter.component": "Компоненты",
"title.oritech.item_filter.toggle_energy_statistics": "", "title.oritech.item_filter.toggle_energy_statistics": "Показать выход",
"tooltip.oritech.addon_silk_touch_desc": "", "tooltip.oritech.addon_silk_touch_desc": "Применяет эффект шёлкового касания к блокам, которые сломаны эндеритовым лазером и разрушителем блоков.",
"tooltip.oritech.augment_research_station": "", "tooltip.oritech.augment_research_station": "Строится на портах исследовательской станции, аналогично портам модулей.",
"tooltip.oritech.augmenter.1": "", "tooltip.oritech.augmenter.1": "Используется для изучения, снятия и установки киберимплантов.",
"tooltip.oritech.augmenter.2": "", "tooltip.oritech.augmenter.2": "Удерживайте [%s] чтобы настроить установленные киберимпланты.",
"tooltip.oritech.electric_mace": "", "tooltip.oritech.electric_mace": "При атаке выпускает небольшую молнию. Работает аналогично обычной булаве.",
"tooltip.oritech.electric_mace.1": "", "tooltip.oritech.electric_mace.1": "Булавы наносят повышенный урон при атаке в падении с большой высоты.",
"tooltip.oritech.fluid_content_tank_tooltip": "", "tooltip.oritech.fluid_content_tank_tooltip": "%fB %s",
"tooltip.oritech.item_filter.component": "", "tooltip.oritech.item_filter.component": "Проверяет все указанные предметы на полное соответствие дата-компонентов",
"tooltip.oritech.item_filter.no_component": "", "tooltip.oritech.item_filter.no_component": "Игнорирует все дата-компоненты предмета, такие как зачарования, энергия и т.д.",
"tooltip.oritech.item_pipe.1": "", "tooltip.oritech.item_pipe.1": "Нажмите ПКМ чтобы включить/выключить извлечение.",
"tooltip.oritech.item_pipe.2": "фыафыаафы", "tooltip.oritech.item_pipe.2": "Извлекает предметы только из первого не пустого слота.",
"tooltip.oritech.item_pipe.3": "", "tooltip.oritech.item_pipe.3": [
"tooltip.oritech.item_pipe.4": "", "Нажмите ПКМ с ",
"tooltip.oritech.laser_arm_block.extra": "", {
"tooltip.oritech.machine.addon_silk_touch": "", "text": "мотором",
"tooltip.oritech.machine.byproduct_bonus.tooltip": "", "color": "#ff830e"
"tooltip.oritech.machine_core_block": "", },
"tooltip.oritech.machine_frame.1": "", " в руке, чтобы улучшить трубу. Теперь она извлекает предметы из всех слотов сразу!"
"tooltip.oritech.motor": "", ],
"tooltip.oritech.oracle_available": "", "tooltip.oritech.item_pipe.4": "Складывает предметы в ближайший доступный инвентарь.",
"tooltip.oritech.oracle_missing": "", "tooltip.oritech.laser_arm_block.extra": "Также можно поставить на стены и потолки.",
"tooltip.oritech.portable_laser.1": "", "tooltip.oritech.machine.addon_silk_touch": "Когда модуль шёлкового касания и модуль удачи устанавливаются одновременно, приоритет отдается модулю шёлкового касания.",
"tooltip.oritech.portable_laser.2": "", "tooltip.oritech.machine.byproduct_bonus.tooltip": "Установлен модуль удачи. Побочные продукты удваиваются.",
"tooltip.oritech.portable_laser.3": "", "tooltip.oritech.machine_core_block": "Улучшенные ядра позволяют подключать больше расширителей модулей а не самих модулей!",
"tooltip.oritech.machine_frame.1": "Максимальная длина: %sx%s блоков.",
"tooltip.oritech.motor": "Нажмите ПКМ по трубе чтобы улучшить её. Теперь она будет извлекать предметы из всех слотов сразу!",
"tooltip.oritech.oracle_available": "Открыть соответствующую страницу справки в Oracle Index.",
"tooltip.oritech.oracle_missing": "Вики-мод Oracle Index не установлен. Справка в игре недоступна.",
"tooltip.oritech.portable_laser.1": "Нажатие ЛКМ выпускает непрерывный луч, который может передавать энергию, ломать блоки, наносить урон",
"tooltip.oritech.portable_laser.2": "и преобразовывать разрушенные предметы как эндеритовый лазер.",
"tooltip.oritech.portable_laser.3": "Нажатие ПКМ создает высокоэнергетический взрыв.",
"tooltip.oritech.portable_laser.4": "", "tooltip.oritech.portable_laser.4": "",
"tooltip.oritech.portable_laser.5": "", "tooltip.oritech.portable_laser.5": "Работает с большинством зачарований для кирки, прочностью и остротой.",
"tooltip.oritech.portable_laser.status.begin": "", "tooltip.oritech.portable_laser.status.begin": "Режим копания:",
"tooltip.oritech.portable_laser.status.hint": "", "tooltip.oritech.portable_laser.status.hint": "(Shift + ПКМ для переключения)",
"tooltip.oritech.portable_laser.status.shot_mining_error": "", "tooltip.oritech.portable_laser.status.shot_mining_error": "Высокоэнергетический взрыв не был произведён, так как включён режим копания.",
"tooltip.oritech.pump_redstone": "", "tooltip.oritech.pump_redstone": "Может быть отключен с помощью редстоун сигнала.",
"tooltip.oritech.reactor_energy_port.rate": "", "tooltip.oritech.reactor_energy_port.rate": "Макс. передача: %s RF/t.",
"tooltip.oritech.refinery_block": "", "tooltip.oritech.refinery_block": "Можно подключить до 2 перегонных кубов на верх очистительного завода.",
"tooltip.oritech.refinery_module": "", "tooltip.oritech.refinery_module": "Можно подключить до 2 перегонных кубов на верх очистительного завода.",
"tooltip.oritech.refinery_module_count": "", "tooltip.oritech.refinery_module_count": "Два дополнительных модуля перегонных кубов могут быть установлены сверху, чтобы разблокировать оставшиеся резервуары.\nОтсутствие модулей удваивает производство жидкости в первом резервуаре.\nОдин модуль удваивает только производство жидкости во втором резервуаре.\nНаличие двух модулей разблокирует все резервуары и не даёт бонусов к производимым жидкостям.",
"tooltip.oritech.steam_engine.1": "", "tooltip.oritech.steam_engine.1": "путём установки двигателей в одну линию. Можно взаимодействовать только",
"tooltip.oritech.steam_engine.2": "", "tooltip.oritech.steam_engine.2": "с первым двигателем. (Например, подавайте пар только в первый двигатель).",
"tooltip.oritech.steam_workers": "", "tooltip.oritech.steam_workers": "Объединенные в цепочку двигатели будут совместно использовать резервуар для пара и воды этого блока.",
"tooltip.oritech.trillion_abbrev": "", "tooltip.oritech.trillion_abbrev": "T",
"tooltip.oritech.unstable_contained_tooltip": "", "tooltip.oritech.unstable_contained_tooltip": "Множитель возможностей сейфа. Чем больше опасных блоков, тем лучше.",
"tooltip.oritech.unstable_container.1": "", "tooltip.oritech.unstable_container.1": "Используется для хранения и преобразования опасных блоков. Нажмите ПКМ по блоку с этим предметом в руке и",
"tooltip.oritech.unstable_container.2": "", "tooltip.oritech.unstable_container.2": "он захватит блок как ядро огромного мультиблочного хранилища энергии.",
"tooltip.oritech.unstable_container.3": "", "tooltip.oritech.unstable_container.3": "Стабилизация сейфа с помощью эндеритового лазера даст экспоненциальное увеличение количества энергии которую он может хранить.",
"tooltip.oritech.unstable_container.4": "", "tooltip.oritech.unstable_container.4": "",
"tooltip.oritech.unstable_container.5": "", "tooltip.oritech.unstable_container.5": "Выходы RF расположены сверху и снизу, а входы с остальных сторон.",
"tooltip.oritech.unstable_container.high": "", "tooltip.oritech.unstable_container.high": "Высокоэнергетические блоки:",
"tooltip.oritech.unstable_container.low": "", "tooltip.oritech.unstable_container.low": "Низкоэнергетические блоки:",
"tooltip.oritech.unstable_container.medium": "", "tooltip.oritech.unstable_container.medium": "Среднеэнергетические блоки:",
"tooltip.oritech.unstable_container_extra_info": "фыфф", "tooltip.oritech.unstable_container_extra_info": "Зажмите shift для просмотра подходящих блоков.",
"tooltip.oritech.unstable_laser_tooltip": "", "tooltip.oritech.unstable_laser_tooltip": "Лазеры не будут добавлять ёмкость этому механизму. Вместо этого они увеличат",
"tooltip.oritech.unstable_laser_tooltip.2": "" "tooltip.oritech.unstable_laser_tooltip.2": "количество блоков которые он может хранить. Это число увеличивается экспоненциально (максимум х5000)."
} }
+3
View File
@@ -5,3 +5,6 @@ numpy==1.23.5
PySocks==1.7.1 PySocks==1.7.1
setuptools==72.1.0 setuptools==72.1.0
wasmtime==35.0.0 wasmtime==35.0.0
flask==3.0.3
deepdiff==8.5.0
python-dotenv==1.0.0
+19 -19
View File
@@ -84,12 +84,12 @@
"tooltip.oritech.item_extra_info": "Зажмите Ctrl для дополнительной информации", "tooltip.oritech.item_extra_info": "Зажмите Ctrl для дополнительной информации",
"tooltip.oritech.addon_desc": "Слоты модулей: ", "tooltip.oritech.addon_desc": "Слоты модулей: ",
"tooltip.oritech.core_desc": "Мультиблок. Требуется ядер механизма: ", "tooltip.oritech.core_desc": "Мультиблок. Требуется ядер механизма: ",
"tooltip.oritech.generator_rate_desc": "Выработка:", "tooltip.oritech.generator_rate_desc": "Генерация:",
"tooltip.oritech.machine_rate_desc": "Потребление энергии: ", "tooltip.oritech.machine_rate_desc": "Потребление энергии: ",
"tooltip.oritech.machine_capacity_desc": "Ёмкость энергии: ", "tooltip.oritech.machine_capacity_desc": "Ёмкость энергии: ",
"tooltip.oritech.solar_generation": "Скорость генерации зависит от качества ядра.", "tooltip.oritech.solar_generation": "Скорость генерации зависит от качества ядра.",
"tooltip.oritech.needs_laser_power": "Требуется питание от эндеритового лазера.", "tooltip.oritech.needs_laser_power": "Требуется питание от эндеритового лазера.",
"tooltip.oritech.steam_engine": "Преобразует пар в RF и воду. Можно объединять в цепочки.", "tooltip.oritech.steam_engine": "Преобразует пар в RF и воду. Можно объединять в цепочки, ",
"tooltip.oritech.item_filter": "Используется для фильтрации предметов которые поступают в инвентарь.", "tooltip.oritech.item_filter": "Используется для фильтрации предметов которые поступают в инвентарь.",
"tooltip.oritech.machine_frame": "Да, в конфигах можно изменить её хитбокс.", "tooltip.oritech.machine_frame": "Да, в конфигах можно изменить её хитбокс.",
"tooltip.oritech.particle_collector": "Собирает энергию тахионов из столкновений частиц.", "tooltip.oritech.particle_collector": "Собирает энергию тахионов из столкновений частиц.",
@@ -123,14 +123,14 @@
"tooltip.oritech.charger": "Заряжает и заправляет оборудование.", "tooltip.oritech.charger": "Заряжает и заправляет оборудование.",
"tooltip.oritech.addon_speed_desc": "Скорость работы ", "tooltip.oritech.addon_speed_desc": "Скорость работы ",
"tooltip.oritech.addon_efficiency_desc": "Энергоэффективность ", "tooltip.oritech.addon_efficiency_desc": "Энергоэффективность ",
"tooltip.oritech.addon_yield_desc": "Удваивает побочные продукты в комплексе переработки. Увеличивает добычу в разрушителе блоков и эндеритовом лазере", "tooltip.oritech.addon_yield_desc": "Удваивает побочные продукты в комплексе переработки. Увеличивает добычу в разрушителе блоков и эндеритовом лазере.",
"tooltip.oritech.processing_addon_desc": "Позволяет обрабатывать +1 рецепт за цикл.", "tooltip.oritech.processing_addon_desc": "Позволяет обрабатывать +1 рецепт за цикл.",
"tooltip.oritech.addon_fluid_desc": "Добавляет обработку с жидкостями (центрифуга, док-станция дронов).", "tooltip.oritech.addon_fluid_desc": "Добавляет обработку с жидкостями (центрифуга, док-станция дронов).",
"tooltip.oritech.addon_acceptor_desc": "Позволяет вводить энергию напрямую в модуль, вместо механизма.", "tooltip.oritech.addon_acceptor_desc": "Позволяет вводить энергию напрямую в модуль, вместо механизма.",
"tooltip.oritech.addon_crop_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Игнорирует незрелые растения и животных", "tooltip.oritech.addon_crop_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Игнорирует незрелые растения и животных.",
"tooltip.oritech.addon_proxy_desc": "Даёт доступ к определённым слотам инвентаря.", "tooltip.oritech.addon_proxy_desc": "Даёт доступ к определённым слотам инвентаря.",
"tooltip.oritech.addon_proxy_select": "Выберите целевой слот", "tooltip.oritech.addon_proxy_select": "Выберите целевой слот",
"tooltip.oritech.addon_quarry_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Увеличивает область работы", "tooltip.oritech.addon_quarry_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Увеличивает область работы.",
"tooltip.oritech.addon_hunter_desc": "Применяется к эндеритовому лазеру. Нацеливается на живых существ.. Турель?", "tooltip.oritech.addon_hunter_desc": "Применяется к эндеритовому лазеру. Нацеливается на живых существ.. Турель?",
"tooltip.oritech.addon_redstone_desc": "Расширенные настройки для редстоуна и компаратора.", "tooltip.oritech.addon_redstone_desc": "Расширенные настройки для редстоуна и компаратора.",
"tooltip.oritech.addon_capacity_desc": "Ёмкость энергии: ", "tooltip.oritech.addon_capacity_desc": "Ёмкость энергии: ",
@@ -259,7 +259,7 @@
"tooltip.oritech.block_destroyer.addon_range": "Макс. глубина добычи", "tooltip.oritech.block_destroyer.addon_range": "Макс. глубина добычи",
"tooltip.oritech.laser_arm.addon_range": "Эффективный радиус луча", "tooltip.oritech.laser_arm.addon_range": "Эффективный радиус луча",
"title.oritech.machine.addon_fortune": "Удача: %d", "title.oritech.machine.addon_fortune": "Удача: %d",
"tooltip.oritech.machine.addon_fortune": "Количество модулей продуктивности.\nЭффективный максимум — 3 штуки.", "tooltip.oritech.machine.addon_fortune": "Количество модулей удачи.\nЭффективный максимум — 3 штуки.",
"tooltip.oritech.machine.quality": "Качество механизма: %d\n\nОпределяет сколько модулей можно установить.\nИспользуйте улучшенные ядра механизма для повышения качества.\nТекущий прогресс качества: %s", "tooltip.oritech.machine.quality": "Качество механизма: %d\n\nОпределяет сколько модулей можно установить.\nИспользуйте улучшенные ядра механизма для повышения качества.\nТекущий прогресс качества: %s",
"tooltip.oritech.thousand_abbrev": "Тыс.", "tooltip.oritech.thousand_abbrev": "Тыс.",
"tooltip.oritech.million_abbrev": "Млн.", "tooltip.oritech.million_abbrev": "Млн.",
@@ -315,14 +315,14 @@
"title.oritech.laser_arm.addon_hunter_damage": "%d Damage", "title.oritech.laser_arm.addon_hunter_damage": "%d Damage",
"tooltip.oritech.laser_arm.addon_hunter_damage": "Эффективный урон луча", "tooltip.oritech.laser_arm.addon_hunter_damage": "Эффективный урон луча",
"title.oritech.machine.addon_looting": "Добыча: %d", "title.oritech.machine.addon_looting": "Добыча: %d",
"tooltip.oritech.machine.addon_looting": "Количество модулей продуктивности.\nЭффективный максимум — 3 штуки.", "tooltip.oritech.machine.addon_looting": "Количество модулей удачи.\nЭффективный максимум — 3 штуки.",
"rei.process.oritech:pulverizer": "Измельчение", "rei.process.oritech:pulverizer": "Измельчение",
"rei.process.oritech:grinder": "Переработка", "rei.process.oritech:grinder": "Переработка",
"rei.process.oritech:assembler": "Сборка", "rei.process.oritech:assembler": "Сборка",
"rei.process.oritech:foundry": "Сплавление", "rei.process.oritech:foundry": "Сплавление",
"rei.process.oritech:cooler": "Охлаждение", "rei.process.oritech:cooler": "Охлаждение",
"rei.process.oritech:centrifuge": "Центрифугирование", "rei.process.oritech:centrifuge": "Центрифугирование",
"rei.process.oritech:centrifuge_fluid": "Разделение жидкостей", "rei.process.oritech:centrifuge_fluid": "Жидкостное центрифугирование",
"rei.process.oritech:atomic_forge": "Атомная реконструкция", "rei.process.oritech:atomic_forge": "Атомная реконструкция",
"rei.process.oritech:lava_generator": "Сжигание лавы", "rei.process.oritech:lava_generator": "Сжигание лавы",
"rei.process.oritech:steam_engine": "Использование пара", "rei.process.oritech:steam_engine": "Использование пара",
@@ -331,14 +331,14 @@
"rei.process.oritech:particle_collision": "Столкновение частиц", "rei.process.oritech:particle_collision": "Столкновение частиц",
"rei.process.oritech:reactor": "Заправка реактора", "rei.process.oritech:reactor": "Заправка реактора",
"rei.process.oritech:laser": "Разрушение блоков лазером", "rei.process.oritech:laser": "Разрушение блоков лазером",
"rei.title.oritech.cookingtime": "%ds (%d тика(ов))", "rei.title.oritech.cookingtime": "%dсек. (%d тиков)",
"emi.category.oritech.pulverizer": "Измельчение", "emi.category.oritech.pulverizer": "Измельчение",
"emi.category.oritech.grinder": "Переработка", "emi.category.oritech.grinder": "Переработка",
"emi.category.oritech.assembler": "Сборка", "emi.category.oritech.assembler": "Сборка",
"emi.category.oritech.foundry": "Сплавление", "emi.category.oritech.foundry": "Сплавление",
"emi.category.oritech.cooler": "Охлаждение", "emi.category.oritech.cooler": "Охлаждение",
"emi.category.oritech.centrifuge": "Центрифугирование", "emi.category.oritech.centrifuge": "Центрифугирование",
"emi.category.oritech.centrifuge_fluid": "Разделение жидкостей", "emi.category.oritech.centrifuge_fluid": "Жидкостное центрифугирование",
"emi.category.oritech.atomic_forge": "Атомная реконструкция", "emi.category.oritech.atomic_forge": "Атомная реконструкция",
"emi.category.oritech.lava_generator": "Сжигание лавы", "emi.category.oritech.lava_generator": "Сжигание лавы",
"emi.category.oritech.steam_engine": "Использование пара", "emi.category.oritech.steam_engine": "Использование пара",
@@ -347,7 +347,7 @@
"emi.category.oritech.particle_collision": "Столкновение частиц", "emi.category.oritech.particle_collision": "Столкновение частиц",
"emi.category.oritech.reactor": "Заправка реактора", "emi.category.oritech.reactor": "Заправка реактора",
"emi.category.oritech.laser": "Разрушение блоков лазером", "emi.category.oritech.laser": "Разрушение блоков лазером",
"emi.title.oritech.cookingtime": "%ds (%d тика(ов))", "emi.title.oritech.cookingtime": "%dсек. (%d тиков)",
"emi.title.oritech.collisionspeed": "Требуемая энергия столкновения: %sJ", "emi.title.oritech.collisionspeed": "Требуемая энергия столкновения: %sJ",
"emi.title.oritech.laser_interaction": "Разрушение блоков лазером", "emi.title.oritech.laser_interaction": "Разрушение блоков лазером",
"itemgroup.oritech.machines": "Механизмы Oritech", "itemgroup.oritech.machines": "Механизмы Oritech",
@@ -450,18 +450,18 @@
"block.oritech.machine_core_5": "Элитное ядро механизма", "block.oritech.machine_core_5": "Элитное ядро механизма",
"block.oritech.machine_core_6": "Ультимативное ядро механизма", "block.oritech.machine_core_6": "Ультимативное ядро механизма",
"block.oritech.machine_core_7": "Совершенное ядро механизма", "block.oritech.machine_core_7": "Совершенное ядро механизма",
"block.oritech.machine_speed_addon": "Модуль: Ускорение", "block.oritech.machine_speed_addon": "Модуль: Разгон",
"block.oritech.machine_processing_addon": "Модуль: Многопоточность", "block.oritech.machine_processing_addon": "Модуль: Многопоточность",
"block.oritech.machine_ultimate_addon": "Модуль: Матрица синергии", "block.oritech.machine_ultimate_addon": "Модуль: Матрица синергии",
"block.oritech.machine_fluid_addon": "Модуль: Резервуар", "block.oritech.machine_fluid_addon": "Модуль: Резервуар",
"block.oritech.machine_yield_addon": "Модуль: Продуктивность", "block.oritech.machine_yield_addon": "Модуль: Удача",
"block.oritech.steam_boiler_addon": "Модуль: Паровой котёл", "block.oritech.steam_boiler_addon": "Модуль: Паровой котёл",
"block.oritech.crop_filter_addon": "Модуль: Фильтр культур", "block.oritech.crop_filter_addon": "Модуль: Фильтр культур",
"block.oritech.quarry_addon": "Модуль: Карьер", "block.oritech.quarry_addon": "Модуль: Карьер",
"block.oritech.machine_hunter_addon": "Модуль: Охотник", "block.oritech.machine_hunter_addon": "Модуль: Охотник",
"block.oritech.machine_efficiency_addon": "Модуль: Эффективность", "block.oritech.machine_efficiency_addon": "Модуль: Энергоэффективность",
"block.oritech.machine_capacitor_addon": "Модуль: Конденсатор", "block.oritech.machine_capacitor_addon": "Модуль: Второй конденсатор",
"block.oritech.machine_acceptor_addon": "Модуль: Интерфейс энергии", "block.oritech.machine_acceptor_addon": "Модуль: Повышающий трансформатор",
"block.oritech.machine_inventory_proxy_addon": "Модуль: Интерфейс инвентаря", "block.oritech.machine_inventory_proxy_addon": "Модуль: Интерфейс инвентаря",
"block.oritech.machine_redstone_addon": "Модуль: Контроль редстоуном", "block.oritech.machine_redstone_addon": "Модуль: Контроль редстоуном",
"block.oritech.capacitor_addon_extender": "Модуль: Продвинутый конденсатор", "block.oritech.capacitor_addon_extender": "Модуль: Продвинутый конденсатор",
@@ -551,7 +551,7 @@
"block.oritech.accelerator_ring": "Направляющее кольцо ускорителя", "block.oritech.accelerator_ring": "Направляющее кольцо ускорителя",
"block.oritech.accelerator_motor": "Линейный двигатель ускорителя", "block.oritech.accelerator_motor": "Линейный двигатель ускорителя",
"block.oritech.accelerator_controller": "Ускоритель частиц", "block.oritech.accelerator_controller": "Ускоритель частиц",
"block.oritech.accelerator_sensor": "Датчик скорости частиц", "block.oritech.accelerator_sensor": "Измеритель скорости частиц",
"block.oritech.reactor_controller": [ "block.oritech.reactor_controller": [
{ {
"text": "Контроллер ядерного реактора", "text": "Контроллер ядерного реактора",
@@ -705,7 +705,7 @@
], ],
"message.oritech.catalyst.cost": "Стоимость: %d", "message.oritech.catalyst.cost": "Стоимость: %d",
"message.oritech.deep_drill.ore_placement": "Механизм должен стоять на месторождениях руды.", "message.oritech.deep_drill.ore_placement": "Механизм должен стоять на месторождениях руды.",
"message.oritech.generator.production_rate": "Выработка: %d (Множитель ядра: %d)", "message.oritech.generator.production_rate": "Генерация: %d (Множитель ядра: %d)",
"message.oritech.machine.missing_core": "Машина не собрана. Установите недостающие ядра механизма.", "message.oritech.machine.missing_core": "Машина не собрана. Установите недостающие ядра механизма.",
"message.oritech.machine_frame.missing_frame": "Отсутствует рама. Постройте прямоугольную раму из блоков рамки механизма", "message.oritech.machine_frame.missing_frame": "Отсутствует рама. Постройте прямоугольную раму из блоков рамки механизма",
"message.oritech.enchanter.insert_item": "Положите предмет", "message.oritech.enchanter.insert_item": "Положите предмет",
@@ -733,7 +733,7 @@
"tooltip.oritech.target_designator.no_target": "Цель не задана", "tooltip.oritech.target_designator.no_target": "Цель не задана",
"tooltip.oritech.pipe_duct_warning": "Соединяется только с трубами. НЕ С МЕХАНИЗМАМИ!", "tooltip.oritech.pipe_duct_warning": "Соединяется только с трубами. НЕ С МЕХАНИЗМАМИ!",
"message.oritech.tool_mode.area_effect": "Копает 3х1", "message.oritech.tool_mode.area_effect": "Копает 3х1",
"message.oritech.tool_mode.silk_touch": "Шёлковое касание", "message.oritech.tool_mode.silk_touch": "Шёлковое касание (При зажатом Shift)",
"tooltip.oritech.tool_mode.area_range.area": "Режим: область", "tooltip.oritech.tool_mode.area_range.area": "Режим: область",
"tooltip.oritech.tool_mode.area_range.single": "Режим: одиночный", "tooltip.oritech.tool_mode.area_range.single": "Режим: одиночный",
"message.oritech.reactor_edge_invalid": "Ошибка: края реактора должны быть прямоугольными", "message.oritech.reactor_edge_invalid": "Ошибка: края реактора должны быть прямоугольными",
+104 -3
View File
@@ -9,6 +9,7 @@
--medium-gray: #ddd; --medium-gray: #ddd;
--dark-gray: #666; --dark-gray: #666;
--purple: #673ab7; --purple: #673ab7;
--pastel-pinpurle: #b73ab7;
--deep-purple: #5c6bc0; --deep-purple: #5c6bc0;
--brown: #795548; --brown: #795548;
} }
@@ -189,7 +190,7 @@ h1 {
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2); box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
} }
.copy-btn, .suggest-btn, .copy-clipboard-btn, .undo-btn, .redo-btn { .copy-btn, .suggest-btn, .copy-clipboard-btn, .undo-btn, .redo-btn, .mark-todo-btn, .resolve-todo-btn{
border: none; border: none;
border-radius: 4px; border-radius: 4px;
padding: 6px 12px; padding: 6px 12px;
@@ -202,6 +203,86 @@ h1 {
gap: 5px; gap: 5px;
} }
.mark-todo-btn {
background-color: var(--warning-color);
color: white;
}
.mark-todo-btn:hover {
background-color: #e68a00;
}
.resolve-todo-btn {
background-color: var(--success-color);
color: white;
}
.resolve-todo-btn:hover {
background-color: #3d8b40;
}
/* Highlight TODO items in translation tab */
.textarea-with-todo {
border-left: 3px solid var(--warning-color);
padding-left: 10px;
}
.translation-input, .new-value, .old-value {
white-space: pre-wrap; /* Preserve both spaces and line breaks */
word-break: break-word; /* Break long words if needed */
}
.show-control-chars::before {
content: "\\A";
white-space: pre;
color: #999;
}
.translation-input {
font-family: Consolas, monospace; /* Use monospace font to better see whitespace */
}
/* For displaying values in diffs */
.new-value, .old-value {
font-family: Consolas, monospace;
display: inline-block;
max-width: 100%;
overflow-x: auto;
background-color: rgba(0,0,0,0.05);
padding: 2px 6px;
border-radius: 3px;
}
/* CAN LEAD TO ERROR */
textarea {
white-space: pre;
overflow-wrap: normal;
overflow-x: auto;
}
/* Стили для визуализации пробелов и переносов */
.whitespace {
position: relative;
}
.whitespace-space::before {
content: "·";
color: #aaa;
position: absolute;
}
.whitespace-tab::before {
content: "→";
color: #99f;
position: absolute;
}
.whitespace-newline {
/* display: inline-block; */
width: 100%;
}
.whitespace-newline::after {
content: "↵";
color: #f99;
margin-left: 2px;
}
.copy-btn { .copy-btn {
background-color: #7fb3d5; background-color: #7fb3d5;
color: white; color: white;
@@ -248,6 +329,14 @@ h1 {
background-color: #5e35b1; background-color: #5e35b1;
} }
.learning-ai {
background-color: var(--pastel-pinpurle) !important;
}
.learning-ai:hover {
background-color: #b1358c !important;
}
.suggestion-btn-row { .suggestion-btn-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -482,7 +571,7 @@ h1 {
.setting-input, .setting-textarea, .setting-select { .setting-input, .setting-textarea, .setting-select {
width: 100%; /* Will take full width of parent .setting-control */ width: 100%; /* Will take full width of parent .setting-control */
max-width: 300px; /* Maximum width to prevent over-expansion */ /* max-width: 300px; */
padding: 8px 12px; padding: 8px 12px;
border: 1px solid var(--medium-gray); border: 1px solid var(--medium-gray);
border-radius: 4px; border-radius: 4px;
@@ -693,4 +782,16 @@ input:checked + .slider:before {
.toast-notification.warning { .toast-notification.warning {
background-color: var(--warning-color); background-color: var(--warning-color);
} }
/* Add this to your CSS */
#markedTodo .key-item {
border-left: 3px solid var(--warning-color);
padding-left: 12px;
}
#markedTodo .new-value {
color: var(--warning-color);
background-color: rgba(255, 152, 0, 0.1);
}
+297 -10
View File
@@ -41,12 +41,47 @@ function copyToTranslation(key, value, inputId) {
setTimeout(() => btn.textContent = originalText, 2000); setTimeout(() => btn.textContent = originalText, 2000);
} }
function visualizeWhitespace(element) {
const text = element.textContent || element.value;
let html = '';
// console.log("Visualating whitespace for: " + text)
if (text === undefined){
return ' '
}
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (char === ' ') {
html += '<span class="whitespace whitespace-space"> </span>';
} else if (char === '\t') {
html += '<span class="whitespace whitespace-tab">\t</span>';
} else if (char === '\n') {
html += '<span class="whitespace-newline">\n</span>';
} else {
html += char;
}
}
return html;
}
function applyWhitespaceVisualization() {
// Для значений в DeepDiff
document.querySelectorAll('.old-value, .new-value').forEach(el => {
el.innerHTML = visualizeWhitespace(el);
});
}
// Modify the copyToClipboard function to preserve newlines
function copyToClipboard(event, text) { function copyToClipboard(event, text) {
event.preventDefault(); event.preventDefault();
const btn = event.currentTarget; const btn = event.currentTarget;
const originalText = btn.textContent; const originalText = btn.textContent;
navigator.clipboard.writeText(text).then(() => { // Preserve the exact text including newlines and spaces
const textToCopy = text.replace(/\\n/g, '\n'); // Convert \n to actual newlines
navigator.clipboard.writeText(textToCopy).then(() => {
btn.textContent = 'Copied!'; btn.textContent = 'Copied!';
btn.classList.add('copied'); btn.classList.add('copied');
setTimeout(() => { setTimeout(() => {
@@ -57,7 +92,7 @@ function copyToClipboard(event, text) {
// Fallback for older browsers // Fallback for older browsers
try { try {
const textarea = document.createElement('textarea'); const textarea = document.createElement('textarea');
textarea.value = text; textarea.value = textToCopy;
document.body.appendChild(textarea); document.body.appendChild(textarea);
textarea.select(); textarea.select();
document.execCommand('copy'); document.execCommand('copy');
@@ -166,7 +201,7 @@ async function getAISuggestions(text, inputId, containerId) {
const response = await fetch('/get_ai_suggestions', { const response = await fetch('/get_ai_suggestions', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({text: text}) body: JSON.stringify({text: text.replace("\n", "\\n")})
}); });
const data = await response.json(); const data = await response.json();
@@ -214,6 +249,69 @@ async function getAISuggestions(text, inputId, containerId) {
} }
} }
async function getLearningAISuggestions(text, inputId, containerId) {
const container = document.getElementById(containerId);
container.style.display = 'block';
container.innerHTML = `
<div class="suggestions-header">
<span>Learning AI Translation Suggestions</span>
<span class="close-suggestions" onclick="closeSuggestions('${containerId}', true)">×</span>
</div>
<div class="loading">Loading Learning AI suggestions...</div>
`;
try {
const response = await fetch('/get_learning_ai_suggestions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({text: text.replace("\n", "\\n")})
});
const data = await response.json();
if (data.error) {
container.innerHTML = `
<div class="suggestions-header">
<span>Error getting Learning AI suggestions</span>
<span class="close-suggestions" onclick="closeSuggestions('${containerId}', true)">×</span>
</div>
<div class="error-message">${data.error}</div>
`;
return;
}
let html = `
<div class="suggestions-header">
<span>Learning AI Translation Suggestions</span>
<span class="close-suggestions" onclick="closeSuggestions('${containerId}', true)">×</span>
</div>
`;
if (data.suggestions?.length > 0) {
data.suggestions.forEach((suggestion, index) => {
html += `
<div class="ai-suggestion" onclick="useSuggestion('${inputId}', '${escapeHtml(suggestion)}')">
<span class="suggestion-number">${index + 1}.</span>
${suggestion}
</div>
`;
});
} else {
html += `<div class="error-message">No Learning AI suggestions available</div>`;
}
container.innerHTML = html;
} catch (error) {
container.innerHTML = `
<div class="suggestions-header">
<span>Error getting Learning AI suggestions</span>
<span class="close-suggestions" onclick="closeSuggestions('${containerId}', true)">×</span>
</div>
<div class="error-message">Error loading Learning AI suggestions: ${error.message}</div>
`;
}
}
function closeSuggestions(containerId, clearContent = false) { function closeSuggestions(containerId, clearContent = false) {
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
container.style.display = 'none'; container.style.display = 'none';
@@ -252,9 +350,14 @@ function openTab(evt, tabName) {
tablinks[i].classList.remove("active"); tablinks[i].classList.remove("active");
} }
if (tabName === 'markedTodo') {
updateTodoTab();
}
document.getElementById(tabName).classList.add("active"); document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active"); evt.currentTarget.classList.add("active");
applyWhitespaceVisualization();
initAutoResizeTextareas(); initAutoResizeTextareas();
} }
@@ -282,11 +385,13 @@ function collectTranslations() {
textareas.forEach(textarea => { textareas.forEach(textarea => {
const key = textarea.id.replace('input-', ''); const key = textarea.id.replace('input-', '');
console.log(key)
const originalKey = document.querySelector(`#translation-${key} .key`).textContent; const originalKey = document.querySelector(`#translation-${key} .key`).textContent;
let value = textarea.value.trim(); let value = textarea.value;
if (value.startsWith("[{")) { if (value.startsWith("[") && value.endsWith("]")) {
translations[originalKey] = eval(value); translations[originalKey] = eval(value);
console.warn("EVALED") console.warn("EVALED: " + value)
console.warn("EVALED: " + eval(value))
} else { } else {
translations[originalKey] = value; translations[originalKey] = value;
} }
@@ -295,15 +400,177 @@ function collectTranslations() {
return translations; return translations;
} }
function markAsTodo(inputId) {
const textarea = document.getElementById(inputId);
if (!textarea.value.includes('//TODO')) {
textarea.value = textarea.value + ' //TODO';
autoResizeTextarea(textarea);
addToHistory(textarea);
showToast('Marked as TODO', 'success');
} else {
showToast('Already marked as TODO', 'info');
}
}
function collectTodoItems() {
const todoItems = [];
const textareas = document.querySelectorAll('#translation .translation-input');
textareas.forEach(textarea => {
if (textarea.value.includes('//TODO') || textarea.value.includes('//REDO')) {
const key = textarea.id.replace('input-', '');
const keyElement = document.querySelector(`#translation-${key} .key`);
const valueElement = document.querySelector(`#translation-${key} .new-value`);
if (keyElement && valueElement) {
todoItems.push({
key: keyElement.textContent,
value: valueElement.textContent,
translation: textarea.value
});
}
}
});
return todoItems;
}
function updateTodoTab() {
const todoTab = document.getElementById('markedTodo');
if (!todoTab) return;
const todoItems = collectTodoItems();
const todoList = todoTab.querySelector('.key-list');
if (todoItems.length === 0) {
todoList.innerHTML = '<p>No TODO/REDO items found. Mark items in the Translation tab by adding //TODO.</p>';
return;
}
let html = '<h3>TODO/REDO Items (' + todoItems.length + ')</h3>';
todoItems.forEach((item, index) => {
html += `
<div class="key-item" id="todo-translation-${index}">
<div class="key-value-row">
<span class="key">${item.key}</span>:
<span class="new-value">${item.value}</span>
</div>
<div class="translation-row">
<textarea class="translation-input"
id="todo-input-${index}"
placeholder="Enter translation...">${item.translation}</textarea>
</div>
<div class="suggestion-btn-row">
<button class="copy-btn" onclick="copyToTranslation('${item.key}', '${escapeHtml(item.value)}', 'todo-input-${index}')">
Copy to Translation
</button>
<button class="copy-clipboard-btn" onclick="copyToClipboard(event, '${escapeHtml(item.value)}')">
Copy to Clipboard
</button>
<button class="suggest-btn" onclick="getTranslationSuggestions('${escapeHtml(item.value)}', 'todo-input-${index}', 'todo_suggestions-${index}')">
Get Translations
</button>
<button class="suggest-btn ai-suggest-btn" onclick="getAISuggestions('${escapeHtml(item.value)}', 'todo-input-${index}', 'todo_ai-suggestions-${index}')">
Get AI Suggestions
</button>
<button class="suggest-btn ai-suggest-btn learning-ai" onclick="getLearningAISuggestions('${escapeHtml(item.value)}', 'todo-input-${index}', 'todo_ai-suggestions-${index}')">
Get Learning AI Suggestions
</button>
<button class="copy-btn" onclick="copyToTranslation('${item.key}', '${escapeHtml(item.value)}', 'todo-input-${index}')">
Copy Original
</button>
<button class="mark-todo-btn" onclick="markAsTodo('input-{{ loop.index }}')">
Mark as TODO
</button>
<button class="resolve-todo-btn" onclick="resolveTodo('todo-input-${index}')">
Resolve TODO
</button>
</div>
<div class="undo-redo-buttons">
<button class="undo-btn" onclick="undoChange('todo-input-${index}')">↩ Undo</button>
<button class="redo-btn" onclick="redoChange('todo-input-${index}')">↪ Redo</button>
</div>
<div class="suggestions-container" id="todo_suggestions-${index}"></div>
<div class="ai-suggestions-container" id="todo_ai-suggestions-${index}"></div>
</div>
`;
});
todoList.innerHTML = html;
initAutoResizeTextareas();
}
function collectTodoTranslations() {
const translations = {};
const textareas = document.querySelectorAll('#markedTodo .translation-input');
textareas.forEach(textarea => {
const key = textarea.id.replace('todo-input-', '');
const originalKey = document.querySelector(`#todo-translation-${key} .key`).textContent;
translations[originalKey] = textarea.value.trim();
});
return translations;
}
function resolveTodo(inputId) {
const textarea = document.getElementById(inputId);
textarea.value = textarea.value.replace('//TODO', '').replace('//REDO', '').trim();
autoResizeTextarea(textarea);
addToHistory(textarea);
showToast('TODO resolved', 'success');
updateTodoTab(); // Refresh the TODO tab
}
async function saveAllTodoTranslations() {
const translations = collectTodoTranslations();
const statusMessage = document.getElementById('todo-status-message');
let state = true;
try {
const response = await fetch('/save_todo_translations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(translations)
.replace(/\\n/g, '\\n') // Явно сохраняем \n
.replace(/\\r/g, '\\r') // И \r если есть
});
const data = await response.json();
if (data.status === 'success') {
statusMessage.textContent = `Successfully saved ${data.saved} TODO translations`;
statusMessage.className = 'status-message success-message';
} else {
statusMessage.textContent = 'Error: ' + (data.message || 'Unknown error');
statusMessage.className = 'status-message error-message';
state = false;
}
setTimeout(() => {
statusMessage.textContent = '';
statusMessage.className = 'status-message';
}, 3000);
return state;
} catch (error) {
statusMessage.textContent = 'Error: ' + error.message;
statusMessage.className = 'status-message error-message';
return false;
}
}
async function saveAllTranslations() { async function saveAllTranslations() {
const translations = collectTranslations(); const translations = collectTranslations();
const statusMessage = document.getElementById('status-message'); const statusMessage = document.getElementById('status-message');
let state = true;
try { try {
const response = await fetch('/save_translations', { const response = await fetch('/save_translations', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(translations) body: JSON.stringify(translations)
.replace(/\\n/g, '\\n') // Явно сохраняем \n
.replace(/\\r/g, '\\r') // И \r если есть
}); });
const data = await response.json(); const data = await response.json();
@@ -313,15 +580,18 @@ async function saveAllTranslations() {
} else { } else {
statusMessage.textContent = 'Error: ' + (data.message || 'Unknown error'); statusMessage.textContent = 'Error: ' + (data.message || 'Unknown error');
statusMessage.className = 'status-message error-message'; statusMessage.className = 'status-message error-message';
state = false;
} }
setTimeout(() => { setTimeout(() => {
statusMessage.textContent = ''; statusMessage.textContent = '';
statusMessage.className = 'status-message'; statusMessage.className = 'status-message';
}, 3000); }, 3000);
return state;
} catch (error) { } catch (error) {
statusMessage.textContent = 'Error: ' + error.message; statusMessage.textContent = 'Error: ' + error.message;
statusMessage.className = 'status-message error-message'; statusMessage.className = 'status-message error-message';
return false;
} }
} }
@@ -413,10 +683,26 @@ document.addEventListener('keydown', e => {
console.log("DEBUG") console.log("DEBUG")
if (e.ctrlKey && (e.key.toLowerCase() === 's' || e.key.toLowerCase() === 'ы')) { if (e.ctrlKey && (e.key.toLowerCase() === 's' || e.key.toLowerCase() === 'ы')) {
e.preventDefault(); e.preventDefault();
const translationTab = document.getElementById('translation'); const activeTab = document.querySelector('.tab.active');
if (translationTab?.classList.contains('active')) { const tabId = activeTab.getAttribute('onclick').split("'")[1];
saveAllTranslations(); let result = false;
if (tabId === 'translation') {
showToast('Saving all translations...', 'info'); showToast('Saving all translations...', 'info');
result = saveAllTranslations();
if (result) {
showToast('Saved all translations!', 'success');
} else {
showToast('Error saving translations.', 'error');
}
} else if (tabId === 'markedTodo') {
showToast('Saving all TODO translations...', 'info');
result = saveAllTodoTranslations();
if (result) {
showToast('Saved all TODO translations!', 'success');
} else {
showToast('Error saving TODO translations.', 'error');
}
} }
} }
@@ -542,6 +828,7 @@ function tabresetSettings() {
// Initialize when page is fully loaded // Initialize when page is fully loaded
window.addEventListener('load', function() { window.addEventListener('load', function() {
applyWhitespaceVisualization();
initAutoResizeTextareas(); initAutoResizeTextareas();
initChangeTracking(); initChangeTracking();
tabinitSettings(); tabinitSettings();
+27 -1
View File
@@ -15,6 +15,7 @@
<div class="tab active" onclick="openTab(event, 'deepdiff')">DeepDiff</div> <div class="tab active" onclick="openTab(event, 'deepdiff')">DeepDiff</div>
<div class="tab" onclick="openTab(event, 'keycheck')">KeyCheck</div> <div class="tab" onclick="openTab(event, 'keycheck')">KeyCheck</div>
<div class="tab" onclick="openTab(event, 'translation')">Translation</div> <div class="tab" onclick="openTab(event, 'translation')">Translation</div>
<div class="tab" onclick="openTab(event, 'markedTodo')">Marked TODO</div>
<div class="tab settings-tab" onclick="openTab(event, 'settings')">⚙ Settings</div> <div class="tab settings-tab" onclick="openTab(event, 'settings')">⚙ Settings</div>
</div> </div>
@@ -113,12 +114,18 @@
<button class="copy-clipboard-btn" onclick="copyToClipboard(event, `{{ item.value }}`)"> <button class="copy-clipboard-btn" onclick="copyToClipboard(event, `{{ item.value }}`)">
Copy to Clipboard Copy to Clipboard
</button> </button>
<button class="mark-todo-btn" onclick="markAsTodo('input-{{ loop.index }}')">
Mark as TODO
</button>
<button class="suggest-btn" onclick="getTranslationSuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `suggestions-{{ loop.index }}`)"> <button class="suggest-btn" onclick="getTranslationSuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `suggestions-{{ loop.index }}`)">
Get Translations Get Translations
</button> </button>
<button class="suggest-btn ai-suggest-btn" onclick="getAISuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `ai-suggestions-{{ loop.index }}`)"> <button class="suggest-btn ai-suggest-btn" onclick="getAISuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `ai-suggestions-{{ loop.index }}`)">
Get AI Suggestions Get AI Suggestions
</button> </button>
<button class="suggest-btn ai-suggest-btn learning-ai" onclick="getLearningAISuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `ai-suggestions-{{ loop.index }}`)">
Get Learning AI Suggestions
</button>
</div> </div>
</div> </div>
<div class="translation-row"> <div class="translation-row">
@@ -140,6 +147,25 @@
</div> </div>
</div> </div>
<div id="markedTodo" class="tab-content">
<h2>Marked TODO/REDO Items</h2>
<div class="translation-controls">
<button class="save-all-btn" onclick="saveAllTodoTranslations()">
Save All TODO Translations (Ctrl+Shift+S)
</button>
<span id="todo-status-message" class="status-message"></span>
<div class="backup-info">Automatic backups are created before each save in the 'backups' folder.</div>
</div>
<div class="key-list">
<h3>TODO/REDO Items (<span id="todo-count">0</span>)</h3>
<div id="todo-items-container">
<p>No TODO/REDO items found. Mark items in the Translation tab by adding //TODO.</p>
</div>
</div>
</div>
<div id="settings" class="tab-content"> <div id="settings" class="tab-content">
<h2>Settings</h2> <h2>Settings</h2>
@@ -206,7 +232,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="settings-actions"> <div class="settings-actions">
<button class="save-settings-btn" onclick="saveSettings()">Save Settings</button> <button class="save-settings-btn" onclick="saveSettings()">Save Settings</button>
<button class="reset-settings-btn" onclick="resetSettings()">Reset to Defaults</button> <button class="reset-settings-btn" onclick="resetSettings()">Reset to Defaults</button>