Major update and bugfixes

Changelog:
This commit is contained in:
f1den
2025-08-01 22:27:07 +03:00
parent e1ed811084
commit 3686b20f23
8 changed files with 877 additions and 369 deletions
+6 -1
View File
@@ -164,5 +164,10 @@ black.py
test.py
dsk/cookies.json
# Development and testing this app
/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()
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:
1. Literal translation
@@ -35,6 +35,34 @@ Format strictly as:
...
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.
"""
@@ -61,7 +89,28 @@ def get_ai_suggestions():
return jsonify({'error': 'No text provided'}), 400
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({
'suggestions': suggestions
@@ -115,6 +164,12 @@ def show_differences():
'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)
existing_translations = jsonUtils.get_existing_translations()
@@ -123,7 +178,8 @@ def show_differences():
key_differences=key_differences,
ru_file=ru_file,
en_file=en_file,
existing_translations=existing_translations)
existing_translations=existing_translations,
todo_items=todo_items)
except Exception as e:
return render_template('error.html', error=str(e))
@@ -141,5 +197,23 @@ def save_translations():
except Exception as e:
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__':
app.run()
+344 -332
View File
@@ -1,11 +1,11 @@
{
"advancements.oritech.augmenter": "Кибернетика!",
"advancements.oritech.augmenter.description": "Преодолейте свои естественные… лимиты! Предупреждение: Может вызвать повышенное желание говорить «бип-буп».",
"advancements.oritech.augmenter": "Киберимпланты!",
"advancements.oritech.augmenter.description": "Преодолейте свои естественные… лимиты! Внимание: Может вызвать повышенное желание взорвать Арасака-тауэр.",
"advancements.oritech.reactor": "Ядерная энергетика",
"advancements.oritech.reactor.description": "Собери реактор. Безопасность? Не, не слышал... Ой, смотри, как красиво светится!",
"block.oritech.advanced_augment_station": [
{
"text": "Квантовая исследовательская станция",
"text": "Станция квантовых исследований",
"color": "#40f5ef"
}
],
@@ -20,336 +20,348 @@
"block.oritech.carbon_plating_pressure_plate": "Нажимная плита из усиленной карбоном обшивки",
"block.oritech.carbon_plating_slab": "Плита из усиленной карбоном обшивки",
"block.oritech.carbon_plating_stairs": "Ступеньки из усиленной карбоном обшивки",
"block.oritech.machine_core_hidden": "",
"block.oritech.machine_silk_touch_addon": "",
"block.oritech.refinery_block": "",
"block.oritech.refinery_module_block": "",
"block.oritech.simple_augment_station": "",
"block.oritech.still_biofuel_block": "",
"block.oritech.still_molten_adamant_block": "",
"block.oritech.still_molten_biosteel_block": "",
"block.oritech.still_molten_duratium_block": "",
"block.oritech.still_molten_energite_block": "",
"block.oritech.still_molten_fluxite": "",
"block.oritech.transparent_item_pipe": "",
"block.oritech.unstable_container": "",
"emi.category.oritech.refinery": "",
"fluid.oritech.still_biofuel": "",
"fluid.oritech.still_diesel": "",
"fluid.oritech.still_heavy_oil": "",
"fluid.oritech.still_mineral_slurry": "",
"fluid.oritech.still_molten_adamant": "",
"fluid.oritech.still_molten_biosteel": "",
"fluid.oritech.still_molten_duratium": "",
"fluid.oritech.still_molten_energite": "",
"fluid.oritech.still_molten_fluxite": "",
"fluid.oritech.still_naphtha": "",
"fluid.oritech.still_sheol_fire": "",
"fluid.oritech.still_silicon_wash": "",
"fluid.oritech.still_strange_matter": "",
"fluid.oritech.still_sulfuric_acid": "",
"item.oritech.clay_catalyst_beads": "",
"item.oritech.electric_mace": "",
"item.oritech.ion_thruster": "",
"item.oritech.portable_laser": "",
"item.oritech.reinforced_carbon_sheet": "",
"item.oritech.still_biofuel_bucket": "",
"item.oritech.still_diesel_bucket": "",
"item.oritech.still_heavy_oil_bucket": "",
"item.oritech.still_mineral_slurry_bucket": "",
"item.oritech.still_molten_adamant_bucket": "",
"item.oritech.still_molten_biosteel_bucket": "",
"item.oritech.still_molten_duratium_bucket": "",
"item.oritech.still_molten_energite_bucket": "",
"item.oritech.still_molten_fluxite_bucket": "",
"item.oritech.still_naphtha_bucket": "",
"item.oritech.still_sheol_fire_bucket": "",
"item.oritech.still_silicon_wash_bucket": "",
"item.oritech.still_strange_matter_bucket": "",
"item.oritech.still_sulfuric_acid_bucket": "",
"item.oritech.unstable_container": "",
"key.oritech.augment_screen": "",
"message.oritech.steamengine.controller_link": "",
"oracle_index.title.oritech": "",
"oritech.text.augment.armor": "",
"oritech.text.augment.armor.desc": "",
"oritech.text.augment.armor.desc.1": "",
"oritech.text.augment.attackdamage": "",
"oritech.text.augment.attackdamage.desc": "фпвыфпв",
"oritech.text.augment.attackdamage.desc.1": "",
"oritech.text.augment.autofeeder": "",
"oritech.text.augment.autofeeder.desc": "",
"oritech.text.augment.autofeeder.desc.1": "",
"oritech.text.augment.autofeeder.desc.2": "",
"oritech.text.augment.betterarmor": "",
"oritech.text.augment.betterarmor.desc": "",
"oritech.text.augment.betterarmor.desc.1": "",
"oritech.text.augment.blockreach": "",
"oritech.text.augment.blockreach.desc": "",
"oritech.text.augment.blockreach.desc.1": "",
"oritech.text.augment.cloak": "",
"oritech.text.augment.cloak.desc": "",
"oritech.text.augment.cloak.desc.1": "",
"oritech.text.augment.cloak.desc.3": "",
"oritech.text.augment.dwarf": "",
"oritech.text.augment.dwarf.desc": "",
"oritech.text.augment.dwarf.desc.1": "",
"oritech.text.augment.dwarf.desc.2": "",
"oritech.text.augment.farblockreach": "",
"oritech.text.augment.farblockreach.desc": "",
"oritech.text.augment.farblockreach.desc.1": "",
"oritech.text.augment.farblockreach.desc.2": "",
"oritech.text.augment.flight": "",
"oritech.text.augment.flight.desc": "",
"oritech.text.augment.flight.desc.1": "",
"oritech.text.augment.giant": "",
"oritech.text.augment.giant.desc": "",
"oritech.text.augment.giant.desc.1": "",
"oritech.text.augment.giant.desc.2": "",
"oritech.text.augment.gravity": "",
"oritech.text.augment.gravity.desc": "",
"oritech.text.augment.gravity.desc.1": "",
"oritech.text.augment.hpboost": "",
"oritech.text.augment.hpboost.desc": "",
"oritech.text.augment.hpboost.desc.1": "",
"oritech.text.augment.hpboostmore": "",
"oritech.text.augment.hpboostmore.desc": "",
"oritech.text.augment.hpboostmore.desc.1": "",
"oritech.text.augment.hpboostultimate": "",
"oritech.text.augment.hpboostultimate.desc": "",
"oritech.text.augment.hpboostultimate.desc.1": "",
"oritech.text.augment.hpboostultra": "",
"oritech.text.augment.hpboostultra.desc": "фыа",
"oritech.text.augment.hpboostultra.desc.1": "",
"oritech.text.augment.luck": "",
"oritech.text.augment.luck.desc": "",
"oritech.text.augment.luck.desc.1": "",
"oritech.text.augment.luck.desc.2": "",
"oritech.text.augment.magnet": "",
"oritech.text.augment.magnet.desc": "",
"oritech.text.augment.magnet.desc.1": "",
"oritech.text.augment.magnet.desc.2": "",
"oritech.text.augment.miningspeed": "",
"oritech.text.augment.miningspeed.desc": "",
"oritech.text.augment.miningspeed.desc.1": "",
"oritech.text.augment.nightvision": "",
"oritech.text.augment.nightvision.desc": "",
"oritech.text.augment.nightvision.desc.1": "",
"oritech.text.augment.nightvision.desc.2": "",
"oritech.text.augment.orefinder": "",
"oritech.text.augment.orefinder.desc": "",
"oritech.text.augment.orefinder.desc.1": "",
"oritech.text.augment.orefinder.desc.2": "",
"oritech.text.augment.portal": "",
"oritech.text.augment.portal.desc": "",
"oritech.text.augment.portal.desc.1": "",
"oritech.text.augment.speedboost": "",
"oritech.text.augment.speedboost.desc": "",
"oritech.text.augment.speedboost.desc.1": "",
"oritech.text.augment.stepassist": "",
"oritech.text.augment.stepassist.desc": "",
"oritech.text.augment.stepassist.desc.1": "",
"oritech.text.augment.stepassist.desc.2": "",
"oritech.text.augment.superattackdamage": "",
"oritech.text.augment.superattackdamage.desc": "",
"oritech.text.augment.superattackdamage.desc.1": "фвып",
"oritech.text.augment.superminingspeed": "",
"oritech.text.augment.superminingspeed.desc": "",
"oritech.text.augment.superminingspeed.desc.1": "",
"oritech.text.augment.superminingspeed.desc.2": "",
"oritech.text.augment.superspeedboost": "",
"oritech.text.augment.superspeedboost.desc": "",
"oritech.text.augment.superspeedboost.desc.1": "",
"oritech.text.augment.superspeedboost.desc.2": "",
"oritech.text.augment.ultimatearmor": "",
"oritech.text.augment.ultimatearmor.desc": "",
"oritech.text.augment.ultimatearmor.desc.1": "",
"oritech.text.augment.waterbreath": "",
"oritech.text.augment.waterbreath.desc": "",
"oritech.text.augment.waterbreath.desc.1": "",
"oritech.text.augment.weaponreach": "",
"oritech.text.augment.weaponreach.desc": "",
"oritech.text.augment.weaponreach.desc.1": "",
"oritech.text.augment_ingredient_tip": "",
"oritech.text.augment_op.apply": "",
"oritech.text.augment_op.pending": "",
"oritech.text.augment_op.remove": "",
"oritech.text.augment_op.research": "",
"oritech.text.augment_research_time": "",
"oritech.text.augment_resource_cost": "",
"oritech.text.augment_toggle": "",
"oritech.text.energy_cost": "",
"oritech.text.missing_requirements_title": "",
"oritech.text.required_station": "",
"rei.process.oritech:refinery": "",
"tag.item.c.bananas": "",
"tag.item.c.biodiesel": "",
"tag.item.c.clumps": "",
"tag.item.c.clumps.copper": "",
"tag.item.c.clumps.gold": "",
"tag.item.c.clumps.iron": "",
"tag.item.c.clumps.nickel": "",
"tag.item.c.clumps.platinum": "",
"tag.item.c.dusts.plutonium": "",
"tag.item.c.dusts.uranium": "",
"tag.item.c.fuels.bio": "",
"tag.item.c.fuels.block.bio": "",
"tag.item.c.high_power_biodiesel": "",
"tag.item.c.oil": "",
"tag.item.c.ores.uranium": "",
"tag.item.c.plastics": "",
"tag.item.c.raw_materials.nickel": "",
"tag.item.c.raw_materials.platinum": "",
"tag.item.c.raw_materials.uranium": "",
"tag.item.c.steam": "",
"tag.item.c.tools.wrench": "",
"tag.item.oritech.biofuel": "",
"tag.item.oritech.biomatter": "",
"tag.item.oritech.reactor_coolant_items": "",
"tag.item.oritech.recyclable.biomass": "",
"tag.item.oritech.recyclable.copper_dust": "",
"tag.item.oritech.recyclable.diamond": "",
"tag.item.oritech.recyclable.gold_dust": "",
"tag.item.oritech.recyclable.gravel": "dsag",
"tag.item.oritech.recyclable.iron_dust": "",
"tag.item.oritech.recyclable.netherite_scrap": "",
"tag.item.oritech.recyclable.quartz_dust_2": "",
"tag.item.oritech.recyclable.quartz_dust_4": "",
"tag.item.oritech.recyclable.red_sand": "",
"tag.item.oritech.recyclable.redstone_dust": "",
"tag.item.oritech.recyclable.sand": "",
"tag.item.oritech.recyclable.small_copper_dust": "",
"tag.item.oritech.recyclable.small_gold_dust": "",
"tag.item.oritech.recyclable.small_iron_dust": "",
"tag.item.oritech.recyclable.string": "",
"tag.item.oritech.turbofuel": "",
"text.config.oritech-config.category.addonConfig": "",
"text.config.oritech-config.category.electricMace": "",
"text.config.oritech-config.category.portableLaserConfig": "",
"text.config.oritech-config.category.processingMachines.refineryData": "",
"text.config.oritech-config.option.acceleratorMotorRFCapacity": "",
"text.config.oritech-config.option.additiveAddons": "",
"text.config.oritech-config.option.additiveAddons.tooltip": "",
"text.config.oritech-config.option.addonConfig.chamberAddonEfficiency": "",
"text.config.oritech-config.option.addonConfig.efficiencyAddonEfficiency": "",
"text.config.oritech-config.option.addonConfig.speedAddonEfficiency": "",
"text.config.oritech-config.option.addonConfig.speedAddonSpeed": "",
"text.config.oritech-config.option.addonConfig.ultimateAddonEfficiency": "",
"text.config.oritech-config.option.addonConfig.ultimateAddonSpeed": "",
"text.config.oritech-config.option.augmenterMaxEnergy": "",
"text.config.oritech-config.option.boringNukes": "",
"text.config.oritech-config.option.catalystHyperExpFactor": "",
"text.config.oritech-config.option.catalystHyperExpFactor.tooltip": "",
"text.config.oritech-config.option.chainsawTreeCutting": "",
"text.config.oritech-config.option.electricMace.baseDamage": "",
"text.config.oritech-config.option.electricMace.chargeSpeed": "",
"text.config.oritech-config.option.electricMace.energyCapacity": "",
"text.config.oritech-config.option.electricMace.energyUsage": "",
"text.config.oritech-config.option.electricMace.lightningCostMultiplier": "",
"text.config.oritech-config.option.enableHelpButton": "",
"text.config.oritech-config.option.generateOresFabricOnly": "",
"text.config.oritech-config.option.generateOresFabricOnly.tooltip": "",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio": "",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio.tooltip": "",
"text.config.oritech-config.option.generators.steamEngineData.steamToRfRatio": "",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull": "",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull.tooltip": "",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull": "",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull.tooltip": "",
"text.config.oritech-config.option.generators.steamId": "",
"text.config.oritech-config.option.generators.steamId.tooltip": "",
"text.config.oritech-config.option.layeredExtenders": "",
"text.config.oritech-config.option.layeredExtenders.tooltip": "",
"text.config.oritech-config.option.portableLaserConfig.blockBreakSpeed": "",
"text.config.oritech-config.option.portableLaserConfig.damageBase": "",
"text.config.oritech-config.option.portableLaserConfig.energyCapacity": "",
"text.config.oritech-config.option.portableLaserConfig.energyPerBoom": "",
"text.config.oritech-config.option.portableLaserConfig.energyPerTick": "",
"text.config.oritech-config.option.portableLaserConfig.explosionStrength": "фывфыв",
"text.config.oritech-config.option.processingMachines.refineryData.energyCapacity": "",
"text.config.oritech-config.option.processingMachines.refineryData.energyPerTick": "",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyExtraction": "",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyInsertion": "",
"text.config.oritech-config.option.reactorMaxEnergyOutput": "",
"text.config.oritech-config.option.reactorMaxEnergyStored": "",
"text.config.oritech-config.option.showMachinePreview": "",
"text.config.oritech-config.option.unstableContainerBaseCapacity": "",
"text.config.oritech-config.section.augments": "",
"text.oritech.augmenter_active": "",
"text.oritech.augmenter_creative_tooltip": "",
"text.oritech.begin_research": "",
"text.oritech.cancel": "",
"text.oritech.install": "",
"text.oritech.load_augments.tooltip": "",
"text.oritech.loaded_augments": "",
"block.oritech.machine_core_hidden": "Цельная обшивка",
"block.oritech.machine_silk_touch_addon": "Модуль: Шелковое касание",
"block.oritech.refinery_block": "Очистительный завод",
"block.oritech.refinery_module_block": "Модуль: Перегонный куб",
"block.oritech.simple_augment_station": "Станция изучения кибернетики",
"block.oritech.still_biofuel_block": "Биотопливо",
"block.oritech.still_molten_adamant_block": "Расплавленный адамантит",
"block.oritech.still_molten_biosteel_block": "Расплавленная биосталь",
"block.oritech.still_molten_duratium_block": "Расплавленный дюратий",
"block.oritech.still_molten_energite_block": "Расплавленный энергит",
"block.oritech.still_molten_fluxite": "Расплавленный флюксит",
"block.oritech.transparent_item_pipe": "Прозрачная предметная труба",
"block.oritech.unstable_container": "Сейф Шредингера",
"emi.category.oritech.refinery": "Очистка",
"fluid.oritech.still_biofuel": "Биотопливо",
"fluid.oritech.still_diesel": "Дизель",
"fluid.oritech.still_heavy_oil": "Тяжелая нефть",
"fluid.oritech.still_mineral_slurry": "Минеральный шлак",
"fluid.oritech.still_molten_adamant": "Расплавленный адамантит",
"fluid.oritech.still_molten_biosteel": "Расплавленная биосталь",
"fluid.oritech.still_molten_duratium": "Расплавленный дюратий",
"fluid.oritech.still_molten_energite": "Расплавленный энергит",
"fluid.oritech.still_molten_fluxite": "Расплавленный флюксит",
"fluid.oritech.still_naphtha": "Лёгкая нафта",
"fluid.oritech.still_sheol_fire": "Шеольский огонь",
"fluid.oritech.still_silicon_wash": "Кремниевый раствор",
"fluid.oritech.still_strange_matter": "Странная материя",
"fluid.oritech.still_sulfuric_acid": "Серная кислота",
"item.oritech.clay_catalyst_beads": "Катализатор из гранулированной глины",
"item.oritech.electric_mace": "Электрическая булава",
"item.oritech.ion_thruster": "Ионный двигатель",
"item.oritech.portable_laser": [
{
"text": "Эндеритовый рельсотрон",
"color": "#ffd505"
}
],
"item.oritech.reinforced_carbon_sheet": "Углепластик",
"item.oritech.still_biofuel_bucket": "Ведро биотоплива",
"item.oritech.still_diesel_bucket": "Ведро дизеля",
"item.oritech.still_heavy_oil_bucket": "Ведро тяжёлой нефти",
"item.oritech.still_mineral_slurry_bucket": "Ведро минерального шлака",
"item.oritech.still_molten_adamant_bucket": "Ведро расплавленного адамантита",
"item.oritech.still_molten_biosteel_bucket": "Ведро расплавленной биостали",
"item.oritech.still_molten_duratium_bucket": "Ведро расплавленного дюратия",
"item.oritech.still_molten_energite_bucket": "Ведро расплавленного энергита",
"item.oritech.still_molten_fluxite_bucket": "Ведро расплавленного флюксита",
"item.oritech.still_naphtha_bucket": "Ведро лёгкой нафты",
"item.oritech.still_sheol_fire_bucket": "Ведро шеольского пламени",
"item.oritech.still_silicon_wash_bucket": "Ведро кремниевого раствора",
"item.oritech.still_strange_matter_bucket": "Ведро странной материи",
"item.oritech.still_sulfuric_acid_bucket": "Ведро серной кислоты",
"item.oritech.unstable_container": "Сейф Шредингера",
"key.oritech.augment_screen": "(Удерживайте) Элементы управления имплантатами [Oritech]//TODO",
"message.oritech.steamengine.controller_link": "Паровой двигатель соединен с основным двигателем (см. выделенный блок). Используйте только основной двигатель.",
"oracle_index.title.oritech": "Oritech",
"oritech.text.augment.armor": "ХУЙ //TODO",
"oritech.text.augment.armor.desc": "//TODO",
"oritech.text.augment.armor.desc.1": "//TODO",
"oritech.text.augment.attackdamage": "//TODO",
"oritech.text.augment.attackdamage.desc": "//TODO",
"oritech.text.augment.attackdamage.desc.1": "//TODO",
"oritech.text.augment.autofeeder": "//TODO",
"oritech.text.augment.autofeeder.desc": "//TODO",
"oritech.text.augment.autofeeder.desc.1": "//TODO",
"oritech.text.augment.autofeeder.desc.2": "//TODO",
"oritech.text.augment.betterarmor": "//TODO",
"oritech.text.augment.betterarmor.desc": "//TODO",
"oritech.text.augment.betterarmor.desc.1": "//TODO",
"oritech.text.augment.blockreach": "//TODO",
"oritech.text.augment.blockreach.desc": "//TODO",
"oritech.text.augment.blockreach.desc.1": "//TODO",
"oritech.text.augment.cloak": "Плащ",
"oritech.text.augment.cloak.desc": "//TODO",
"oritech.text.augment.cloak.desc.1": "//TODO",
"oritech.text.augment.cloak.desc.3": "//TODO",
"oritech.text.augment.dwarf": "//TODO",
"oritech.text.augment.dwarf.desc": "//TODO",
"oritech.text.augment.dwarf.desc.1": "//TODO",
"oritech.text.augment.dwarf.desc.2": "//TODO",
"oritech.text.augment.farblockreach": "//TODO",
"oritech.text.augment.farblockreach.desc": "//TODO",
"oritech.text.augment.farblockreach.desc.1": "//TODO",
"oritech.text.augment.farblockreach.desc.2": "//TODO",
"oritech.text.augment.flight": "Полёт",
"oritech.text.augment.flight.desc": "//TODO",
"oritech.text.augment.flight.desc.1": "//TODO",
"oritech.text.augment.giant": "Гигантизм",
"oritech.text.augment.giant.desc": "//TODO",
"oritech.text.augment.giant.desc.1": "//TODO",
"oritech.text.augment.giant.desc.2": "//TODO",
"oritech.text.augment.gravity": "//TODO",
"oritech.text.augment.gravity.desc": "//TODO",
"oritech.text.augment.gravity.desc.1": "//TODO",
"oritech.text.augment.hpboost": "//TODO",
"oritech.text.augment.hpboost.desc": "//TODO",
"oritech.text.augment.hpboost.desc.1": "//TODO",
"oritech.text.augment.hpboostmore": "//TODO",
"oritech.text.augment.hpboostmore.desc": "//TODO",
"oritech.text.augment.hpboostmore.desc.1": "//TODO",
"oritech.text.augment.hpboostultimate": "//TODO",
"oritech.text.augment.hpboostultimate.desc": "//TODO",
"oritech.text.augment.hpboostultimate.desc.1": "//TODO",
"oritech.text.augment.hpboostultra": "//TODO",
"oritech.text.augment.hpboostultra.desc": "//TODO",
"oritech.text.augment.hpboostultra.desc.1": "//TODO",
"oritech.text.augment.luck": "Удача",
"oritech.text.augment.luck.desc": "//TODO",
"oritech.text.augment.luck.desc.1": "//TODO",
"oritech.text.augment.luck.desc.2": "//TODO",
"oritech.text.augment.magnet": "Магнит",
"oritech.text.augment.magnet.desc": "//TODO",
"oritech.text.augment.magnet.desc.1": "//TODO",
"oritech.text.augment.magnet.desc.2": "//TODO",
"oritech.text.augment.miningspeed": "//TODO",
"oritech.text.augment.miningspeed.desc": "//TODO",
"oritech.text.augment.miningspeed.desc.1": "//TODO",
"oritech.text.augment.nightvision": "Ночное зрение",
"oritech.text.augment.nightvision.desc": "//TODO",
"oritech.text.augment.nightvision.desc.1": "//TODO",
"oritech.text.augment.nightvision.desc.2": "//TODO",
"oritech.text.augment.orefinder": "//TODO",
"oritech.text.augment.orefinder.desc": "//TODO",
"oritech.text.augment.orefinder.desc.1": "//TODO",
"oritech.text.augment.orefinder.desc.2": "//TODO",
"oritech.text.augment.portal": "//TODO",
"oritech.text.augment.portal.desc": "//TODO",
"oritech.text.augment.portal.desc.1": "//TODO",
"oritech.text.augment.speedboost": "//TODO",
"oritech.text.augment.speedboost.desc": "//TODO",
"oritech.text.augment.speedboost.desc.1": "//TODO",
"oritech.text.augment.stepassist": "//TODO",
"oritech.text.augment.stepassist.desc": "//TODO",
"oritech.text.augment.stepassist.desc.1": "//TODO",
"oritech.text.augment.stepassist.desc.2": "//TODO",
"oritech.text.augment.superattackdamage": "//TODO",
"oritech.text.augment.superattackdamage.desc": "//TODO",
"oritech.text.augment.superattackdamage.desc.1": "//TODO",
"oritech.text.augment.superminingspeed": "//TODO",
"oritech.text.augment.superminingspeed.desc": "//TODO",
"oritech.text.augment.superminingspeed.desc.1": "//TODO",
"oritech.text.augment.superminingspeed.desc.2": "//TODO",
"oritech.text.augment.superspeedboost": "//TODO",
"oritech.text.augment.superspeedboost.desc": "//TODO",
"oritech.text.augment.superspeedboost.desc.1": "//TODO",
"oritech.text.augment.superspeedboost.desc.2": "//TODO",
"oritech.text.augment.ultimatearmor": "//TODO",
"oritech.text.augment.ultimatearmor.desc": "//TODO",
"oritech.text.augment.ultimatearmor.desc.1": "//TODO",
"oritech.text.augment.waterbreath": "//TODO",
"oritech.text.augment.waterbreath.desc": "//TODO",
"oritech.text.augment.waterbreath.desc.1": "//TODO",
"oritech.text.augment.weaponreach": "//TODO",
"oritech.text.augment.weaponreach.desc": "//TODO",
"oritech.text.augment.weaponreach.desc.1": "//TODO",
"oritech.text.augment_ingredient_tip": "//TODO",
"oritech.text.augment_op.apply": "Применить",
"oritech.text.augment_op.pending": "Изучение...",
"oritech.text.augment_op.remove": "Удалить \\ Снять //TODO",
"oritech.text.augment_op.research": "Изучить \\ Изучение //TODO",
"oritech.text.augment_research_time": "//TODO",
"oritech.text.augment_resource_cost": "//TODO",
"oritech.text.augment_toggle": "//TODO",
"oritech.text.energy_cost": "//TODO",
"oritech.text.missing_requirements_title": "//TODO",
"oritech.text.required_station": "//TODO",
"rei.process.oritech:refinery": "Очистка",
"tag.item.c.bananas": "Бананы",
"tag.item.c.biodiesel": "Биодизель",
"tag.item.c.clumps": "Глыбы",
"tag.item.c.clumps.copper": "Медные глыбы",
"tag.item.c.clumps.gold": "Золотые глыбы",
"tag.item.c.clumps.iron": "Железные глыбы",
"tag.item.c.clumps.nickel": "Никелевая пыль",
"tag.item.c.clumps.platinum": "Платиновая пыль",
"tag.item.c.dusts.plutonium": "Плутониевая пыль",
"tag.item.c.dusts.uranium": "Урановая пыль",
"tag.item.c.fuels.bio": "Био топливо",
"tag.item.c.fuels.block.bio": "Блоки биотоплива",
"tag.item.c.high_power_biodiesel": "Высокоэффективный биодизель",
"tag.item.c.oil": "Нефть",
"tag.item.c.ores.uranium": "Урановые руды",
"tag.item.c.plastics": "Пластик",
"tag.item.c.raw_materials.nickel": "Необработанный никель",
"tag.item.c.raw_materials.platinum": "Необработанную платину",
"tag.item.c.raw_materials.uranium": "Необработанный уран",
"tag.item.c.steam": "Пар",
"tag.item.c.tools.wrench": "Гаечные ключи",
"tag.item.oritech.biofuel": "Биотопливо",
"tag.item.oritech.biomatter": "Биоматерия",
"tag.item.oritech.reactor_coolant_items": "Хладагенты",
"tag.item.oritech.recyclable.biomass": "Перерабатывается в биомассу",
"tag.item.oritech.recyclable.copper_dust": "Перерабатывается в медную пыль",
"tag.item.oritech.recyclable.diamond": "Перерабатывается в алмаз",
"tag.item.oritech.recyclable.gold_dust": "Перерабатывается в золотую пыль",
"tag.item.oritech.recyclable.gravel": "Перерабатывается в гравий",
"tag.item.oritech.recyclable.iron_dust": "Перерабатывается в железную пыль",
"tag.item.oritech.recyclable.netherite_scrap": "Перерабатывается в незеритовый лом",
"tag.item.oritech.recyclable.quartz_dust_2": "Перерабатывается в кварцевую пыль (2)",
"tag.item.oritech.recyclable.quartz_dust_4": "Перерабатывается в кварцевую пыль (4)",
"tag.item.oritech.recyclable.red_sand": "Перерабатывается в красный песок",
"tag.item.oritech.recyclable.redstone_dust": "Перерабатывается в редстоуновую пыль",
"tag.item.oritech.recyclable.sand": "Перерабатывается в песок",
"tag.item.oritech.recyclable.small_copper_dust": "Перерабатывается в маленькую кучку медной пыли",
"tag.item.oritech.recyclable.small_gold_dust": "Перерабатывается в маленькую кучку золотой пыли",
"tag.item.oritech.recyclable.small_iron_dust": "Перерабатывается в маленькую кучку железной пыли",
"tag.item.oritech.recyclable.string": "Перерабатывается в нить",
"tag.item.oritech.turbofuel": "Турботопливо",
"text.config.oritech-config.category.addonConfig": "Данные Модуля",
"text.config.oritech-config.category.electricMace": "Электрическая булава",
"text.config.oritech-config.category.portableLaserConfig": "Эндеритовый рельсотрон",
"text.config.oritech-config.category.processingMachines.refineryData": "Настройки очистительного завода",
"text.config.oritech-config.option.acceleratorMotorRFCapacity": "Емкость RF: Линейный двигатель ускорителя",
"text.config.oritech-config.option.additiveAddons": "Тип множителя модулей",
"text.config.oritech-config.option.additiveAddons.tooltip": "Позволяет выбрать тип множителя для модулей: сложение и перемножение.",
"text.config.oritech-config.option.addonConfig.chamberAddonEfficiency": "//TODO",
"text.config.oritech-config.option.addonConfig.efficiencyAddonEfficiency": "//TODO",
"text.config.oritech-config.option.addonConfig.speedAddonEfficiency": "//TODO",
"text.config.oritech-config.option.addonConfig.speedAddonSpeed": "//TODO",
"text.config.oritech-config.option.addonConfig.ultimateAddonEfficiency": "//TODO",
"text.config.oritech-config.option.addonConfig.ultimateAddonSpeed": "//TODO",
"text.config.oritech-config.option.augmenterMaxEnergy": "//TODO",
"text.config.oritech-config.option.boringNukes": "//TODO",
"text.config.oritech-config.option.catalystHyperExpFactor": "//TODO",
"text.config.oritech-config.option.catalystHyperExpFactor.tooltip": "//TODO",
"text.config.oritech-config.option.chainsawTreeCutting": "//TODO",
"text.config.oritech-config.option.electricMace.baseDamage": "//TODO",
"text.config.oritech-config.option.electricMace.chargeSpeed": "//TODO",
"text.config.oritech-config.option.electricMace.energyCapacity": "//TODO",
"text.config.oritech-config.option.electricMace.energyUsage": "//TODO",
"text.config.oritech-config.option.electricMace.lightningCostMultiplier": "//TODO",
"text.config.oritech-config.option.enableHelpButton": "//TODO",
"text.config.oritech-config.option.generateOresFabricOnly": "//TODO",
"text.config.oritech-config.option.generateOresFabricOnly.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.rfToSteamRatio.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.steamToRfRatio": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnEnergyFull.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull": "//TODO",
"text.config.oritech-config.option.generators.steamEngineData.stopOnWaterFull.tooltip": "//TODO",
"text.config.oritech-config.option.generators.steamId": "//TODO",
"text.config.oritech-config.option.generators.steamId.tooltip": "//TODO",
"text.config.oritech-config.option.layeredExtenders": "//TODO",
"text.config.oritech-config.option.layeredExtenders.tooltip": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.blockBreakSpeed": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.damageBase": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyCapacity": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyPerBoom": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.energyPerTick": "//TODO",
"text.config.oritech-config.option.portableLaserConfig.explosionStrength": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.energyCapacity": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.energyPerTick": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyExtraction": "//TODO",
"text.config.oritech-config.option.processingMachines.refineryData.maxEnergyInsertion": "//TODO",
"text.config.oritech-config.option.reactorMaxEnergyOutput": "//TODO",
"text.config.oritech-config.option.reactorMaxEnergyStored": "//TODO",
"text.config.oritech-config.option.showMachinePreview": "//TODO",
"text.config.oritech-config.option.unstableContainerBaseCapacity": "//TODO",
"text.config.oritech-config.section.augments": "//TODO",
"text.oritech.augmenter_active": "Осталось: %s сек.",
"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.open_inv.tooltip": "",
"text.oritech.remove": "",
"text.oritech.unstable_container_blocked": "",
"title.oritech.energy.inAvgSecond": "",
"title.oritech.energy.inAvgSecond.tooltip": "",
"title.oritech.energy.inLastTick": "",
"title.oritech.energy.inLastTick.tooltip": "",
"title.oritech.energy.inPeak": "",
"title.oritech.energy.inPeak.tooltip": "",
"title.oritech.energy.inSources": "",
"title.oritech.energy.inSources.tooltip": "",
"title.oritech.energy.outAvgSecond": "",
"title.oritech.energy.outAvgSecond.tooltip": "",
"title.oritech.energy.outLastTick": "",
"title.oritech.energy.outLastTick.tooltip": "",
"title.oritech.energy.outPeak": "",
"title.oritech.energy.outPeak.tooltip": "",
"title.oritech.item_filter.component": "",
"title.oritech.item_filter.toggle_energy_statistics": "",
"tooltip.oritech.addon_silk_touch_desc": "",
"tooltip.oritech.augment_research_station": "",
"tooltip.oritech.augmenter.1": "",
"tooltip.oritech.augmenter.2": "",
"tooltip.oritech.electric_mace": "",
"tooltip.oritech.electric_mace.1": "",
"tooltip.oritech.fluid_content_tank_tooltip": "",
"tooltip.oritech.item_filter.component": "",
"tooltip.oritech.item_filter.no_component": "",
"tooltip.oritech.item_pipe.1": "",
"tooltip.oritech.item_pipe.2": "фыафыаафы",
"tooltip.oritech.item_pipe.3": "",
"tooltip.oritech.item_pipe.4": "",
"tooltip.oritech.laser_arm_block.extra": "",
"tooltip.oritech.machine.addon_silk_touch": "",
"tooltip.oritech.machine.byproduct_bonus.tooltip": "",
"tooltip.oritech.machine_core_block": "",
"tooltip.oritech.machine_frame.1": "",
"tooltip.oritech.motor": "",
"tooltip.oritech.oracle_available": "",
"tooltip.oritech.oracle_missing": "",
"tooltip.oritech.portable_laser.1": "",
"tooltip.oritech.portable_laser.2": "",
"tooltip.oritech.portable_laser.3": "",
"text.oritech.open_inv.tooltip": "Открыть центр кибернетических улучшений",
"text.oritech.remove": "Убрать",
"text.oritech.unstable_container_blocked": "Невозможно поставить, область заблокирована.",
"title.oritech.energy.inAvgSecond": "∅ %s RF/t",
"title.oritech.energy.inAvgSecond.tooltip": "Средний RF/t полученный за последнюю секунду.",
"title.oritech.energy.inLastTick": "📈 %s RF/t",
"title.oritech.energy.inLastTick.tooltip": "Всего RF полученных за последний тик.",
"title.oritech.energy.inPeak": "📊 %s RF/t",
"title.oritech.energy.inPeak.tooltip": "Пиковый RF/t полученный за последнюю секунду.",
"title.oritech.energy.inSources": "%s Источник(ов)",
"title.oritech.energy.inSources.tooltip": "Количество событий получения RF за последний тик\n(Например, количество механизмов/блоков которые отдали энергию).",
"title.oritech.energy.outAvgSecond": "∅ %s RF/t",
"title.oritech.energy.outAvgSecond.tooltip": "Средняя отдача RF/t за последнюю секунду.",
"title.oritech.energy.outLastTick": "📈 %s RF/t",
"title.oritech.energy.outLastTick.tooltip": "Количество RF, которое было отдано из этого блока за последний тик.",
"title.oritech.energy.outPeak": "📊 %s RF/t",
"title.oritech.energy.outPeak.tooltip": "Пиковая отдача RF/t за последнюю секунду.",
"title.oritech.item_filter.component": "Компоненты",
"title.oritech.item_filter.toggle_energy_statistics": "Показать выход",
"tooltip.oritech.addon_silk_touch_desc": "Применяет эффект шёлкового касания к блокам, которые сломаны эндеритовым лазером и разрушителем блоков.",
"tooltip.oritech.augment_research_station": "Строится на портах исследовательской станции, аналогично портам модулей.",
"tooltip.oritech.augmenter.1": "Используется для изучения, снятия и установки киберимплантов.",
"tooltip.oritech.augmenter.2": "Удерживайте [%s] чтобы настроить установленные киберимпланты.",
"tooltip.oritech.electric_mace": "При атаке выпускает небольшую молнию. Работает аналогично обычной булаве.",
"tooltip.oritech.electric_mace.1": "Булавы наносят повышенный урон при атаке в падении с большой высоты.",
"tooltip.oritech.fluid_content_tank_tooltip": "%fB %s",
"tooltip.oritech.item_filter.component": "Проверяет все указанные предметы на полное соответствие дата-компонентов",
"tooltip.oritech.item_filter.no_component": "Игнорирует все дата-компоненты предмета, такие как зачарования, энергия и т.д.",
"tooltip.oritech.item_pipe.1": "Нажмите ПКМ чтобы включить/выключить извлечение.",
"tooltip.oritech.item_pipe.2": "Извлекает предметы только из первого не пустого слота.",
"tooltip.oritech.item_pipe.3": [
"Нажмите ПКМ с ",
{
"text": "мотором",
"color": "#ff830e"
},
" в руке, чтобы улучшить трубу. Теперь она извлекает предметы из всех слотов сразу!"
],
"tooltip.oritech.item_pipe.4": "Складывает предметы в ближайший доступный инвентарь.",
"tooltip.oritech.laser_arm_block.extra": "Также можно поставить на стены и потолки.",
"tooltip.oritech.machine.addon_silk_touch": "Когда модуль шёлкового касания и модуль удачи устанавливаются одновременно, приоритет отдается модулю шёлкового касания.",
"tooltip.oritech.machine.byproduct_bonus.tooltip": "Установлен модуль удачи. Побочные продукты удваиваются.",
"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.5": "",
"tooltip.oritech.portable_laser.status.begin": "",
"tooltip.oritech.portable_laser.status.hint": "",
"tooltip.oritech.portable_laser.status.shot_mining_error": "",
"tooltip.oritech.pump_redstone": "",
"tooltip.oritech.reactor_energy_port.rate": "",
"tooltip.oritech.refinery_block": "",
"tooltip.oritech.refinery_module": "",
"tooltip.oritech.refinery_module_count": "",
"tooltip.oritech.steam_engine.1": "",
"tooltip.oritech.steam_engine.2": "",
"tooltip.oritech.steam_workers": "",
"tooltip.oritech.trillion_abbrev": "",
"tooltip.oritech.unstable_contained_tooltip": "",
"tooltip.oritech.unstable_container.1": "",
"tooltip.oritech.unstable_container.2": "",
"tooltip.oritech.unstable_container.3": "",
"tooltip.oritech.portable_laser.5": "Работает с большинством зачарований для кирки, прочностью и остротой.",
"tooltip.oritech.portable_laser.status.begin": "Режим копания:",
"tooltip.oritech.portable_laser.status.hint": "(Shift + ПКМ для переключения)",
"tooltip.oritech.portable_laser.status.shot_mining_error": "Высокоэнергетический взрыв не был произведён, так как включён режим копания.",
"tooltip.oritech.pump_redstone": "Может быть отключен с помощью редстоун сигнала.",
"tooltip.oritech.reactor_energy_port.rate": "Макс. передача: %s RF/t.",
"tooltip.oritech.refinery_block": "Можно подключить до 2 перегонных кубов на верх очистительного завода.",
"tooltip.oritech.refinery_module": "Можно подключить до 2 перегонных кубов на верх очистительного завода.",
"tooltip.oritech.refinery_module_count": "Два дополнительных модуля перегонных кубов могут быть установлены сверху, чтобы разблокировать оставшиеся резервуары.\nОтсутствие модулей удваивает производство жидкости в первом резервуаре.\nОдин модуль удваивает только производство жидкости во втором резервуаре.\nНаличие двух модулей разблокирует все резервуары и не даёт бонусов к производимым жидкостям.",
"tooltip.oritech.steam_engine.1": "путём установки двигателей в одну линию. Можно взаимодействовать только",
"tooltip.oritech.steam_engine.2": "с первым двигателем. (Например, подавайте пар только в первый двигатель).",
"tooltip.oritech.steam_workers": "Объединенные в цепочку двигатели будут совместно использовать резервуар для пара и воды этого блока.",
"tooltip.oritech.trillion_abbrev": "T",
"tooltip.oritech.unstable_contained_tooltip": "Множитель возможностей сейфа. Чем больше опасных блоков, тем лучше.",
"tooltip.oritech.unstable_container.1": "Используется для хранения и преобразования опасных блоков. Нажмите ПКМ по блоку с этим предметом в руке и",
"tooltip.oritech.unstable_container.2": "он захватит блок как ядро огромного мультиблочного хранилища энергии.",
"tooltip.oritech.unstable_container.3": "Стабилизация сейфа с помощью эндеритового лазера даст экспоненциальное увеличение количества энергии которую он может хранить.",
"tooltip.oritech.unstable_container.4": "",
"tooltip.oritech.unstable_container.5": "",
"tooltip.oritech.unstable_container.high": "",
"tooltip.oritech.unstable_container.low": "",
"tooltip.oritech.unstable_container.medium": "",
"tooltip.oritech.unstable_container_extra_info": "фыфф",
"tooltip.oritech.unstable_laser_tooltip": "",
"tooltip.oritech.unstable_laser_tooltip.2": ""
"tooltip.oritech.unstable_container.5": "Выходы RF расположены сверху и снизу, а входы с остальных сторон.",
"tooltip.oritech.unstable_container.high": "Высокоэнергетические блоки:",
"tooltip.oritech.unstable_container.low": "Низкоэнергетические блоки:",
"tooltip.oritech.unstable_container.medium": "Среднеэнергетические блоки:",
"tooltip.oritech.unstable_container_extra_info": "Зажмите shift для просмотра подходящих блоков.",
"tooltip.oritech.unstable_laser_tooltip": "Лазеры не будут добавлять ёмкость этому механизму. Вместо этого они увеличат",
"tooltip.oritech.unstable_laser_tooltip.2": "количество блоков которые он может хранить. Это число увеличивается экспоненциально (максимум х5000)."
}
+3
View File
@@ -5,3 +5,6 @@ numpy==1.23.5
PySocks==1.7.1
setuptools==72.1.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.addon_desc": "Слоты модулей: ",
"tooltip.oritech.core_desc": "Мультиблок. Требуется ядер механизма: ",
"tooltip.oritech.generator_rate_desc": "Выработка:",
"tooltip.oritech.generator_rate_desc": "Генерация:",
"tooltip.oritech.machine_rate_desc": "Потребление энергии: ",
"tooltip.oritech.machine_capacity_desc": "Ёмкость энергии: ",
"tooltip.oritech.solar_generation": "Скорость генерации зависит от качества ядра.",
"tooltip.oritech.needs_laser_power": "Требуется питание от эндеритового лазера.",
"tooltip.oritech.steam_engine": "Преобразует пар в RF и воду. Можно объединять в цепочки.",
"tooltip.oritech.steam_engine": "Преобразует пар в RF и воду. Можно объединять в цепочки, ",
"tooltip.oritech.item_filter": "Используется для фильтрации предметов которые поступают в инвентарь.",
"tooltip.oritech.machine_frame": "Да, в конфигах можно изменить её хитбокс.",
"tooltip.oritech.particle_collector": "Собирает энергию тахионов из столкновений частиц.",
@@ -123,14 +123,14 @@
"tooltip.oritech.charger": "Заряжает и заправляет оборудование.",
"tooltip.oritech.addon_speed_desc": "Скорость работы ",
"tooltip.oritech.addon_efficiency_desc": "Энергоэффективность ",
"tooltip.oritech.addon_yield_desc": "Удваивает побочные продукты в комплексе переработки. Увеличивает добычу в разрушителе блоков и эндеритовом лазере",
"tooltip.oritech.addon_yield_desc": "Удваивает побочные продукты в комплексе переработки. Увеличивает добычу в разрушителе блоков и эндеритовом лазере.",
"tooltip.oritech.processing_addon_desc": "Позволяет обрабатывать +1 рецепт за цикл.",
"tooltip.oritech.addon_fluid_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_select": "Выберите целевой слот",
"tooltip.oritech.addon_quarry_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Увеличивает область работы",
"tooltip.oritech.addon_quarry_desc": "Применяется к разрушителю блоков и эндеритовому лазеру. Увеличивает область работы.",
"tooltip.oritech.addon_hunter_desc": "Применяется к эндеритовому лазеру. Нацеливается на живых существ.. Турель?",
"tooltip.oritech.addon_redstone_desc": "Расширенные настройки для редстоуна и компаратора.",
"tooltip.oritech.addon_capacity_desc": "Ёмкость энергии: ",
@@ -259,7 +259,7 @@
"tooltip.oritech.block_destroyer.addon_range": "Макс. глубина добычи",
"tooltip.oritech.laser_arm.addon_range": "Эффективный радиус луча",
"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.thousand_abbrev": "Тыс.",
"tooltip.oritech.million_abbrev": "Млн.",
@@ -315,14 +315,14 @@
"title.oritech.laser_arm.addon_hunter_damage": "%d Damage",
"tooltip.oritech.laser_arm.addon_hunter_damage": "Эффективный урон луча",
"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:grinder": "Переработка",
"rei.process.oritech:assembler": "Сборка",
"rei.process.oritech:foundry": "Сплавление",
"rei.process.oritech:cooler": "Охлаждение",
"rei.process.oritech:centrifuge": "Центрифугирование",
"rei.process.oritech:centrifuge_fluid": "Разделение жидкостей",
"rei.process.oritech:centrifuge_fluid": "Жидкостное центрифугирование",
"rei.process.oritech:atomic_forge": "Атомная реконструкция",
"rei.process.oritech:lava_generator": "Сжигание лавы",
"rei.process.oritech:steam_engine": "Использование пара",
@@ -331,14 +331,14 @@
"rei.process.oritech:particle_collision": "Столкновение частиц",
"rei.process.oritech:reactor": "Заправка реактора",
"rei.process.oritech:laser": "Разрушение блоков лазером",
"rei.title.oritech.cookingtime": "%ds (%d тика(ов))",
"rei.title.oritech.cookingtime": "%dсек. (%d тиков)",
"emi.category.oritech.pulverizer": "Измельчение",
"emi.category.oritech.grinder": "Переработка",
"emi.category.oritech.assembler": "Сборка",
"emi.category.oritech.foundry": "Сплавление",
"emi.category.oritech.cooler": "Охлаждение",
"emi.category.oritech.centrifuge": "Центрифугирование",
"emi.category.oritech.centrifuge_fluid": "Разделение жидкостей",
"emi.category.oritech.centrifuge_fluid": "Жидкостное центрифугирование",
"emi.category.oritech.atomic_forge": "Атомная реконструкция",
"emi.category.oritech.lava_generator": "Сжигание лавы",
"emi.category.oritech.steam_engine": "Использование пара",
@@ -347,7 +347,7 @@
"emi.category.oritech.particle_collision": "Столкновение частиц",
"emi.category.oritech.reactor": "Заправка реактора",
"emi.category.oritech.laser": "Разрушение блоков лазером",
"emi.title.oritech.cookingtime": "%ds (%d тика(ов))",
"emi.title.oritech.cookingtime": "%dсек. (%d тиков)",
"emi.title.oritech.collisionspeed": "Требуемая энергия столкновения: %sJ",
"emi.title.oritech.laser_interaction": "Разрушение блоков лазером",
"itemgroup.oritech.machines": "Механизмы Oritech",
@@ -450,18 +450,18 @@
"block.oritech.machine_core_5": "Элитное ядро механизма",
"block.oritech.machine_core_6": "Ультимативное ядро механизма",
"block.oritech.machine_core_7": "Совершенное ядро механизма",
"block.oritech.machine_speed_addon": "Модуль: Ускорение",
"block.oritech.machine_speed_addon": "Модуль: Разгон",
"block.oritech.machine_processing_addon": "Модуль: Многопоточность",
"block.oritech.machine_ultimate_addon": "Модуль: Матрица синергии",
"block.oritech.machine_fluid_addon": "Модуль: Резервуар",
"block.oritech.machine_yield_addon": "Модуль: Продуктивность",
"block.oritech.machine_yield_addon": "Модуль: Удача",
"block.oritech.steam_boiler_addon": "Модуль: Паровой котёл",
"block.oritech.crop_filter_addon": "Модуль: Фильтр культур",
"block.oritech.quarry_addon": "Модуль: Карьер",
"block.oritech.machine_hunter_addon": "Модуль: Охотник",
"block.oritech.machine_efficiency_addon": "Модуль: Эффективность",
"block.oritech.machine_capacitor_addon": "Модуль: Конденсатор",
"block.oritech.machine_acceptor_addon": "Модуль: Интерфейс энергии",
"block.oritech.machine_efficiency_addon": "Модуль: Энергоэффективность",
"block.oritech.machine_capacitor_addon": "Модуль: Второй конденсатор",
"block.oritech.machine_acceptor_addon": "Модуль: Повышающий трансформатор",
"block.oritech.machine_inventory_proxy_addon": "Модуль: Интерфейс инвентаря",
"block.oritech.machine_redstone_addon": "Модуль: Контроль редстоуном",
"block.oritech.capacitor_addon_extender": "Модуль: Продвинутый конденсатор",
@@ -551,7 +551,7 @@
"block.oritech.accelerator_ring": "Направляющее кольцо ускорителя",
"block.oritech.accelerator_motor": "Линейный двигатель ускорителя",
"block.oritech.accelerator_controller": "Ускоритель частиц",
"block.oritech.accelerator_sensor": "Датчик скорости частиц",
"block.oritech.accelerator_sensor": "Измеритель скорости частиц",
"block.oritech.reactor_controller": [
{
"text": "Контроллер ядерного реактора",
@@ -705,7 +705,7 @@
],
"message.oritech.catalyst.cost": "Стоимость: %d",
"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_frame.missing_frame": "Отсутствует рама. Постройте прямоугольную раму из блоков рамки механизма",
"message.oritech.enchanter.insert_item": "Положите предмет",
@@ -733,7 +733,7 @@
"tooltip.oritech.target_designator.no_target": "Цель не задана",
"tooltip.oritech.pipe_duct_warning": "Соединяется только с трубами. НЕ С МЕХАНИЗМАМИ!",
"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.single": "Режим: одиночный",
"message.oritech.reactor_edge_invalid": "Ошибка: края реактора должны быть прямоугольными",
+104 -3
View File
@@ -9,6 +9,7 @@
--medium-gray: #ddd;
--dark-gray: #666;
--purple: #673ab7;
--pastel-pinpurle: #b73ab7;
--deep-purple: #5c6bc0;
--brown: #795548;
}
@@ -189,7 +190,7 @@ h1 {
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-radius: 4px;
padding: 6px 12px;
@@ -202,6 +203,86 @@ h1 {
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 {
background-color: #7fb3d5;
color: white;
@@ -248,6 +329,14 @@ h1 {
background-color: #5e35b1;
}
.learning-ai {
background-color: var(--pastel-pinpurle) !important;
}
.learning-ai:hover {
background-color: #b1358c !important;
}
.suggestion-btn-row {
display: flex;
flex-wrap: wrap;
@@ -482,7 +571,7 @@ h1 {
.setting-input, .setting-textarea, .setting-select {
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;
border: 1px solid var(--medium-gray);
border-radius: 4px;
@@ -693,4 +782,16 @@ input:checked + .slider:before {
.toast-notification.warning {
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);
}
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) {
event.preventDefault();
const btn = event.currentTarget;
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.classList.add('copied');
setTimeout(() => {
@@ -57,7 +92,7 @@ function copyToClipboard(event, text) {
// Fallback for older browsers
try {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.value = textToCopy;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
@@ -166,7 +201,7 @@ async function getAISuggestions(text, inputId, containerId) {
const response = await fetch('/get_ai_suggestions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({text: text})
body: JSON.stringify({text: text.replace("\n", "\\n")})
});
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) {
const container = document.getElementById(containerId);
container.style.display = 'none';
@@ -252,9 +350,14 @@ function openTab(evt, tabName) {
tablinks[i].classList.remove("active");
}
if (tabName === 'markedTodo') {
updateTodoTab();
}
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
applyWhitespaceVisualization();
initAutoResizeTextareas();
}
@@ -282,11 +385,13 @@ function collectTranslations() {
textareas.forEach(textarea => {
const key = textarea.id.replace('input-', '');
console.log(key)
const originalKey = document.querySelector(`#translation-${key} .key`).textContent;
let value = textarea.value.trim();
if (value.startsWith("[{")) {
let value = textarea.value;
if (value.startsWith("[") && value.endsWith("]")) {
translations[originalKey] = eval(value);
console.warn("EVALED")
console.warn("EVALED: " + value)
console.warn("EVALED: " + eval(value))
} else {
translations[originalKey] = value;
}
@@ -295,15 +400,177 @@ function collectTranslations() {
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() {
const translations = collectTranslations();
const statusMessage = document.getElementById('status-message');
let state = true;
try {
const response = await fetch('/save_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();
@@ -313,15 +580,18 @@ async function saveAllTranslations() {
} 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;
}
}
@@ -413,10 +683,26 @@ document.addEventListener('keydown', e => {
console.log("DEBUG")
if (e.ctrlKey && (e.key.toLowerCase() === 's' || e.key.toLowerCase() === 'ы')) {
e.preventDefault();
const translationTab = document.getElementById('translation');
if (translationTab?.classList.contains('active')) {
saveAllTranslations();
const activeTab = document.querySelector('.tab.active');
const tabId = activeTab.getAttribute('onclick').split("'")[1];
let result = false;
if (tabId === 'translation') {
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
window.addEventListener('load', function() {
applyWhitespaceVisualization();
initAutoResizeTextareas();
initChangeTracking();
tabinitSettings();
+27 -1
View File
@@ -15,6 +15,7 @@
<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, '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>
@@ -113,12 +114,18 @@
<button class="copy-clipboard-btn" onclick="copyToClipboard(event, `{{ item.value }}`)">
Copy to Clipboard
</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 }}`)">
Get Translations
</button>
<button class="suggest-btn ai-suggest-btn" onclick="getAISuggestions(`{{ item.value }}`, `input-{{ loop.index }}`, `ai-suggestions-{{ loop.index }}`)">
Get AI Suggestions
</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 class="translation-row">
@@ -140,6 +147,25 @@
</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">
<h2>Settings</h2>
@@ -206,7 +232,7 @@
</div>
</div>
</div>
<div class="settings-actions">
<button class="save-settings-btn" onclick="saveSettings()">Save Settings</button>
<button class="reset-settings-btn" onclick="resetSettings()">Reset to Defaults</button>