Фиксы/Перевод (#296)
* upd * upd * upd * add localization tools * upd * upd * upd * upd * upd * Update book-salvage.ftl * Update book-authorbooks.ftl * Update implant.ftl * fix * fix * fix
This commit is contained in:
parent
012be1afc2
commit
3fc5d92b2d
96 changed files with 1931 additions and 236 deletions
|
|
@ -173,7 +173,8 @@ public sealed class SunriseCCVars
|
|||
public static readonly CVarDef<float> LobbyOpacity =
|
||||
CVarDef.Create("lobby.lobby_opacity", 0.90f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<string> ServerName = CVarDef.Create("lobby.server_name", "SS14", CVar.REPLICATED);
|
||||
public static readonly CVarDef<string> ServerName =
|
||||
CVarDef.Create("lobby.server_name", "SS14", CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Planet Prison
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
ent-ActionInfect = Заразить [10 ОЭ]
|
||||
.desc = Попытка заразить существо [-10 очков эволюции]. Первые три попытки заражения не требуют очков эволюции. Выберите способность и нажмите ЛКМ по сущности, чтобы заразить.
|
||||
ent-ActionDiseaseShop = Мутация
|
||||
.desc = Открывает меню мутации
|
||||
ent-ActionDiseaseBaseChance = Улучшение заражения. [20 ОЭ]
|
||||
.desc = Действие 'заразить' имеет больший шанс на успех. [-20 очков эволюции]
|
||||
ent-ActionDiseaseInfectChance = Повышение заразности. [15 ОЭ]
|
||||
.desc = Увеличивает заразность вируса. Влияет на шанс заражения. [-15 очков эволюции]
|
||||
ent-ActionDiseaseShield = Повышение устойчивости. [15 ОЭ]
|
||||
.desc = Повышает устойчивость болезни к лекарствам. Влияет на скорость лечения при употреблении лекарства. [-15 очков эволюции]
|
||||
ent-ActionDiseaseLethal = Повышение летальности. [15 ОЭ]
|
||||
.desc = Увеличивает получаемый урон от симптомов. Влияет на получаемый при кашле урон. [-15 очков эволюции]
|
||||
ent-ActionDiseaseCough = Кашель
|
||||
.desc = Заражённые начинают кашлять.
|
||||
ent-ActionDiseaseSneeze = Чих
|
||||
.desc = Заражённые начинают чихать.
|
||||
ent-ActionDiseaseVomit = Тошнота
|
||||
.desc = Заражённых начинает тоншить, вызывая рвоту.
|
||||
ent-ActionDiseaseNarcolepsy = Сонливость
|
||||
.desc = У заражённых появляется постоянное желание спать, с которым они иногда не могут справиться.
|
||||
ent-ActionDiseaseCrying = Непроизвольные слёзы
|
||||
.desc = У заражённых активно слезятся глаза, из-за чего кажется, что они плачут.
|
||||
ent-ActionDiseaseMuted = Немота
|
||||
.desc = Мутация вызывает повреждение подъязычного нерва, приводя к параличу мышц языка, из-за чего больные теряют возможность нормально говорить.
|
||||
ent-ActionDiseaseSlowness = Изнеможение
|
||||
.desc = Вирус вызывает разрушение мышечных волокон, приводящее к атрофие и сопровождающееся слабостью. Снижает общую мобильность
|
||||
ent-ActionDiseaseBleed = Кровопотеря
|
||||
.desc = Вирус вызывает денатурацию гемоглобина крови, из-за чего у всех носителей появляется тяжелая степень анемии
|
||||
ent-ActionDiseaseBlindness = Слепота
|
||||
.desc = Длительная болезнь приводит к отмиранию зрительного нерва, что приводит к практически полной слепоте больного.
|
||||
ent-ActionDiseaseInsult = Судороги
|
||||
.desc = Длительная болезнь вызывает гиперстимуляцию двигательных нейронов, в результате чего больные могут испытывать перенапряжение мышц, приводящие к судорогам.
|
||||
|
|
@ -4,3 +4,5 @@ ent-ClothingHeadsetMagistrate = гарнитура Магистрата
|
|||
.desc = Гарнитура магистрата, чтобы услышать последние слова зеков.
|
||||
ent-ClothingHeadsetLawyer = гарнитура Юриста
|
||||
.desc = Гарнитура юриста, что-бы защитить честь клоуна.
|
||||
ent-ClothingHeadsetJEngineering = гарнитура тюремного инженерного отдела.
|
||||
.desc = Гарнитура, которую используют тюремные инженеры.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ent-DrinkFourteenLokoPlusCan = банка Фоуртин Локо плюс
|
||||
.desc = Ремейк от Мэйсона Миллера популярного напитка Фоуртин Локо, но также как и в оригинале, пожалуйста пейте ответственно.
|
||||
ent-DrinkLukin = банка напитка Лукина
|
||||
.desc = Не очень известный алкогольный напиток Лукина. Он обладает лечебными свойствами в деле телесных повреждений, но не стоит пить слишком много...
|
||||
.desc = Не очень известный алкогольный напиток Лукина. Он обладает лечебными свойствами в деле телесных повреждений, но не стоит пить слишком много...
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
ent-FoodSnackMaysonEnergy = энергетический батончик Мэйсона
|
||||
.desc = Энергетический батончик состоящий из натуральных химикатов!
|
||||
ent-FoodSnackMaysonEnergyBar = энергетический батончик Мэйсона
|
||||
.desc = Энергетический батончик состоящий из натуральных химикатов!
|
||||
.desc = Энергетический батончик состоящий из натуральных химикатов!
|
||||
ent-FoodPacketMaysonEnergyTrash = обёртка энергетического батончика
|
||||
.desc = { ent-FoodPacketTrash.desc }
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
ent-Boris = роботический мозг
|
||||
.desc = Искусственный мозг, способной к банальной интелектуальной активности.
|
||||
ent-Sofia = роботический мозг синдиката
|
||||
.desc = Искусственный мозг, способной к банальной интелектуальной активности.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ent-Boris = роботический мозг
|
||||
.desc = Искусственный мозг, способной к банальной интелектуальной активности.
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
ent-BigThruster = большой ракетный двигатель
|
||||
.desc = { ent-Thruster }
|
||||
.desc = { ent-Thruster }
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
ent-EvilTwinSpawn = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ent-EvilTwin = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ent-FugitiveSpawn = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
ent-Fugitive = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
ent-DiseaseSpawn = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
ent-FugitiveSpawn = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
ent-EvilTwinSpawn = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
ent-DiseaseRule = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
ent-Fugitive = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
ent-EvilTwin = { ent-BaseGameRule }
|
||||
.desc = { ent-BaseGameRule.desc }
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ent-MobDisease = разумная болезнь
|
||||
.desc = plague inc 2.0
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
ent-StatueFurukai = статуя Фурукай
|
||||
.desc = { ent-BaseStructure.desc }
|
||||
ent-StatueDude = статуя Дюда
|
||||
.desc = { ent-BaseStructure.desc }
|
||||
ent-StatueFrank = статуя Фрэнка
|
||||
.desc = { ent-BaseStructure.desc }
|
||||
ent-StatueMooniverse = статуя Лунивёрса
|
||||
.desc = { ent-BaseStructure.desc }
|
||||
ent-StatueRib = статуя Риба
|
||||
.desc = { ent-BaseStructure.desc }
|
||||
|
|
@ -2,10 +2,6 @@ mob-name-disease = Разумная болезнь
|
|||
mob-description-disease = plague inc 2.0
|
||||
ghostrole-disease-name = разумная болезнь
|
||||
ghostrole-disease-description = Обычная болезнь, которая вдруг решила стать разумной. С кем не бывает?
|
||||
action-disease-infect-name = Заразить [10 ОЭ]
|
||||
action-disease-infect-description = Попытка заразить существо [-10 очков эволюции]. Первые три попытки заражения не требуют очков эволюции. Выберите способность и нажмите ЛКМ по сущности, чтобы заразить.
|
||||
action-disease-shop-name = Мутация
|
||||
action-disease-shop-description = Открывает меню мутации
|
||||
disease-round-end-result = [color=green]На станции была разумная болезнь. Ей было:[/color]
|
||||
disease-round-end-result-infected = Заражено: [color=gold]{ $count }[/color]
|
||||
disease-round-end-result-infects = Заражённых в конце раунда: [color=gold]{ $count }[/color]
|
||||
|
|
|
|||
|
|
@ -2,33 +2,5 @@ shop-disease-category-infect = Передача
|
|||
shop-disease-category-symptoms = Симптомы
|
||||
shop-disease-category-evolution = Улучшение
|
||||
shop-disease-currency = Очки эволюции
|
||||
listing-disease-cough-name = Кашель
|
||||
listing-disease-cough-description = Заражённые начинают кашлять.
|
||||
listing-disease-sneeze-name = Чих
|
||||
listing-disease-sneeze-description = Заражённые начинают чихать.
|
||||
listing-disease-vomit-name = Тошнота
|
||||
listing-disease-vomit-description = Заражённых начинает тоншить, вызывая рвоту.
|
||||
listing-disease-zombie-name = Зомбирование
|
||||
listing-disease-zombie-description = 70% текущих заражённых становятся зомби. Они так-же перестают быть заражёнными.
|
||||
listing-disease-cry-name = Непроизвольные слёзы
|
||||
listing-disease-cry-description = У заражённых активно слезятся глаза, из-за чего кажется, что они плачут.
|
||||
listing-disease-base-chance-name = Улучшение заражения. [20 ОЭ]
|
||||
listing-disease-base-chance-description = Действие 'заразить' имеет больший шанс на успех. [-20 очков эволюции]
|
||||
listing-disease-infect-chance-name = Повышение заразности. [15 ОЭ]
|
||||
listing-disease-infect-chance-description = Увеличивает заразность вируса. Влияет на шанс заражения. [-15 очков эволюции]
|
||||
listing-disease-shield-name = Повышение устойчивости. [15 ОЭ]
|
||||
listing-disease-shield-description = Повышает устойчивость болезни к лекарствам. Влияет на скорость лечения при употреблении лекарства. [-15 очков эволюции]
|
||||
listing-disease-lethal-name = Повышение летальности. [15 ОЭ]
|
||||
listing-disease-lethal-description = Увеличивает получаемый урон от симптомов. Влияет на получаемый при кашле урон. [-15 очков эволюции]
|
||||
listing-disease-narcolepsy-name = Сонливость
|
||||
listing-disease-narcolepsy-description = У заражённых появляется постоянное желание спать, с которым они иногда не могут справиться.
|
||||
listing-disease-muted-name = Немота
|
||||
listing-disease-muted-description = Мутация вызывает повреждение подъязычного нерва, приводя к параличу мышц языка, из-за чего больные теряют возможность нормально говорить.
|
||||
listing-disease-slowness-name = Изнеможение
|
||||
listing-disease-slowness-description = Вирус вызывает разрушение мышечных волокон, приводящее к атрофие и сопровождающееся слабостью. Снижает общую мобильность
|
||||
listing-disease-bleed-name = Кровопотеря
|
||||
listing-disease-bleed-description = Вирус вызывает денатурацию гемоглобина крови, из-за чего у всех носителей появляется тяжелая степень анемии
|
||||
listing-disease-blindness-name = Слепота
|
||||
listing-disease-blindness-description = Длительная болезнь приводит к отмиранию зрительного нерва, что приводит к практически полной слепоте больного.
|
||||
listing-disease-insult-name = Судороги
|
||||
listing-disease-insult-description = Длительная болезнь вызывает гиперстимуляцию двигательных нейронов, в результате чего больные могут испытывать перенапряжение мышц, приводящие к судорогам.
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
ent-ClothingHeadsetJEngineering = гарнитура тюремного инженерного отдела.
|
||||
.desc = Гарнитура, которую используют тюремные инженеры.
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
reagent-name-fourteen-loko-plus = фоуртин локо плюс
|
||||
reagent-desc-fourteen-loko-plus = Сильно переработанная жидкая субстанция с элиментами стимулирующих веществ, едва ли соответствующая межгалактическим стандартам безопасности для безалкогольного напитка.
|
||||
reagent-desc-fourteen-loko-plus = Сильно переработанная жидкая субстанция с элиментами стимулирующих веществ, едва ли соответствующая межгалактическим стандартам безопасности для безалкогольного напитка.
|
||||
|
|
|
|||
134
Resources/Locale/ru-RU/accent/german.ftl
Normal file
134
Resources/Locale/ru-RU/accent/german.ftl
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
accent-german-words-1 = да
|
||||
accent-german-words-replace-1 = ja
|
||||
accent-german-words-2 = нет
|
||||
accent-german-words-replace-2 = nein
|
||||
accent-german-words-3 = тот
|
||||
accent-german-words-replace-3 = Das
|
||||
accent-german-words-4 = дерьмо
|
||||
accent-german-words-replace-4 = scheisse
|
||||
accent-german-words-5 = сосиска
|
||||
accent-german-words-replace-5 = wurst
|
||||
accent-german-words-6 = сосиски
|
||||
accent-german-words-replace-6 = würste
|
||||
accent-german-words-7 = мужик
|
||||
accent-german-words-replace-7 = mann
|
||||
accent-german-words-8 = мужики
|
||||
accent-german-words-replace-8 = männer
|
||||
accent-german-words-9 = женщина
|
||||
accent-german-words-9-2 = леди
|
||||
accent-german-words-replace-9 = frau
|
||||
accent-german-words-10 = женщины
|
||||
accent-german-words-10-2 = дамы
|
||||
accent-german-words-replace-10 = frauen
|
||||
accent-german-words-11 = джентельмен
|
||||
accent-german-words-replace-11 = herr
|
||||
accent-german-words-12 = джентельмены
|
||||
accent-german-words-replace-12 = herren
|
||||
accent-german-words-13 = мой бог
|
||||
accent-german-words-replace-13 = mein gott
|
||||
accent-german-words-14 = мой
|
||||
accent-german-words-replace-14 = mein
|
||||
accent-german-words-15 = здесь
|
||||
accent-german-words-replace-15 = hier
|
||||
accent-german-words-16 = идиот
|
||||
accent-german-words-replace-16 = dummkopf
|
||||
accent-german-words-17 = идиоты
|
||||
accent-german-words-replace-17 = dummköpfe
|
||||
accent-german-words-18 = бабочка
|
||||
accent-german-words-replace-18 = schmetterling
|
||||
accent-german-words-19 = машина
|
||||
accent-german-words-replace-19 = maschine
|
||||
accent-german-words-replace-19-2 = auto
|
||||
accent-german-words-20 = машины
|
||||
accent-german-words-replace-20 = maschinen
|
||||
accent-german-words-21 = осторожно
|
||||
accent-german-words-replace-21 = achtung
|
||||
accent-german-words-22 = музыка
|
||||
accent-german-words-replace-22 = musik
|
||||
accent-german-words-23 = капитан
|
||||
accent-german-words-replace-23 = kapitän
|
||||
accent-german-words-24 = кебаб
|
||||
accent-german-words-replace-24 = döner
|
||||
accent-german-words-25 = мышь
|
||||
accent-german-words-replace-25 = maus
|
||||
accent-german-words-26 = что
|
||||
accent-german-words-replace-26 = wat
|
||||
accent-german-words-27 = спасибо вам
|
||||
accent-german-words-replace-27 = dankeschön
|
||||
accent-german-words-28 = спасибо
|
||||
accent-german-words-replace-28 = danke
|
||||
accent-german-words-29 = будьте здоровы
|
||||
accent-german-words-replace-29 = gesundheit
|
||||
accent-german-words-30 = огнемёт
|
||||
accent-german-words-replace-30 = flammenwerfer
|
||||
accent-german-words-31 = призрак
|
||||
accent-german-words-replace-31 = poltergeist
|
||||
accent-german-words-32 = сорняк
|
||||
accent-german-words-32-2 = cabbage
|
||||
accent-german-words-replace-32 = kraut
|
||||
accent-german-words-33 = вотка
|
||||
accent-german-words-replace-33 = wodka
|
||||
accent-german-words-34 = рюкзак
|
||||
accent-german-words-replace-34 = rucksack
|
||||
accent-german-words-35 = медицина
|
||||
accent-german-words-replace-35 = medizin
|
||||
accent-german-words-36 = акцент
|
||||
accent-german-words-replace-36 = akzent
|
||||
accent-german-words-37 = аномалия
|
||||
accent-german-words-replace-37 = anomalie
|
||||
accent-german-words-38 = артефакт
|
||||
accent-german-words-38-2 = artefact
|
||||
accent-german-words-replace-38 = artefakt
|
||||
accent-german-words-39 = тупой
|
||||
accent-german-words-replace-39 = dumm
|
||||
accent-german-words-40 = глупый
|
||||
accent-german-words-replace-40 = doof
|
||||
accent-german-words-41 = замечательный
|
||||
accent-german-words-41-2 = прекрастный
|
||||
accent-german-words-replace-41 = wunderbar
|
||||
accent-german-words-42 = опастно
|
||||
accent-german-words-replace-42 = warnung
|
||||
accent-german-words-43 = опастности
|
||||
accent-german-words-replace-43 = warnungen
|
||||
accent-german-words-44 = и
|
||||
accent-german-words-replace-44 = und
|
||||
accent-german-words-45 = карп
|
||||
accent-german-words-replace-45 = karpfen
|
||||
accent-german-words-46 = командир
|
||||
accent-german-words-replace-46 = kommandant
|
||||
accent-german-words-47 = пиво
|
||||
accent-german-words-replace-47 = bier
|
||||
accent-german-words-48 = привет
|
||||
accent-german-words-replace-48 = hallo
|
||||
accent-german-words-49 = здравия
|
||||
accent-german-words-replace-49 = guten Tag
|
||||
accent-german-words-50 = помощь
|
||||
accent-german-words-replace-50 = hilfe
|
||||
accent-german-words-51 = до свидания
|
||||
accent-german-words-replace-51 = auf Wiedersehen
|
||||
accent-german-words-52 = пока
|
||||
accent-german-words-replace-52 = tschüss
|
||||
accent-german-words-53 = пока пока
|
||||
accent-german-words-53-2 = пока-пока
|
||||
accent-german-words-replace-53 = tschau
|
||||
accent-german-words-54 = фантастически
|
||||
accent-german-words-replace-54 = fantastisch
|
||||
accent-german-words-55 = генокрад
|
||||
accent-german-words-replace-55 = doppelgänger
|
||||
accent-german-words-56 = запрещенный
|
||||
accent-german-words-replace-56 = verboten
|
||||
accent-german-words-57 = быстрый
|
||||
accent-german-words-57-2 = быстро
|
||||
accent-german-words-replace-57 = schnell
|
||||
accent-german-words-58 = мед
|
||||
accent-german-words-replace-58 = krankenhaus
|
||||
accent-german-words-59 = катушка теслы
|
||||
accent-german-words-replace-59 = tesla coil
|
||||
accent-german-words-60 = катушки теслы
|
||||
accent-german-words-replace-60 = tesla coils
|
||||
accent-german-words-61 = teslaloose
|
||||
accent-german-words-61-2 = tesloose
|
||||
accent-german-words-61-3 = lightning ball
|
||||
accent-german-words-61-4 = шаровая молния
|
||||
accent-german-words-61-5 = тесла
|
||||
accent-german-words-replace-61 = kugelblitz
|
||||
|
|
@ -5,7 +5,7 @@ player-panel-bans = Всего банов: { $totalBans }
|
|||
player-panel-rolebans = Всего роль банов: { $totalRoleBans }
|
||||
player-panel-notes = Всего заметок: { $totalNotes }
|
||||
player-panel-playtime = Всего наиграно времени: { $days }д:{ $hours }ч:{ $minutes }м
|
||||
player-panel-shared-connections = Shared Connections: { $sharedConnections }
|
||||
player-panel-shared-connections = Общие подключения: { $sharedConnections }
|
||||
player-panel-copy-username = Копировать
|
||||
player-panel-show-notes = Заметки
|
||||
player-panel-show-bans = Показать баны
|
||||
|
|
@ -18,4 +18,5 @@ player-panel-ban = Бан
|
|||
player-panel-logs = Логи
|
||||
player-panel-delete = Удалить
|
||||
player-panel-rejuvenate = Воскресить
|
||||
player-panel-false = False
|
||||
player-panel-false = Нет
|
||||
player-panel-true = Да
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
## Entity
|
||||
|
||||
gas-analyzer-object-out-of-range = Объект вышел за пределы зоны обнаружения.
|
||||
gas-analyzer-component-player-cannot-reach-message = Вы не можете туда достать.
|
||||
gas-analyzer-shutoff = Газоанализатор выключается.
|
||||
|
||||
|
|
|
|||
2
Resources/Locale/ru-RU/botany/seeds.ftl
Normal file
2
Resources/Locale/ru-RU/botany/seeds.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
botany-plant-seedsrestored = Слышен едва уловимый звук шуршания листьев.
|
||||
botany-plant-seedsdestroyed = Семена на растении начинают трещать и падать!
|
||||
|
|
@ -128,6 +128,7 @@ bounty-description-torch = Шеф-повар оживил всех обезья
|
|||
bounty-description-medkit-box = Центком ставит спектакль в больнице, и нуждается в реквизите. Просто пришлите нам несколько пустых аптечек, и спектакль состоится!
|
||||
bounty-description-cardobard-box = Завтра состоится премьера новой пьесы "Пришествие картонборга", а костюмеры совершенно не готовы. Пришлите нам несколько коробок для оформления.
|
||||
bounty-description-wine = Новая библиотекарша и квартирмейстер влюбились друг в друга по уши после того, как она застала его за разборкой книжных полок на дрова. Пришлите пару бутылок вина (или банок, если хотите), чтобы свидание прошло удачно.
|
||||
bounty-description-cardboard-box = "The Cardborgs Cometh" is a new play premiering tomorrow, and the costuming team is woefully unprepared. Send us some boxes to work with.
|
||||
bounty-description-cotton-boll = Огромный рой таракамолей съел всю бумагу и ткань на станции. Пришлите нам немного хлопка, чтобы наши крылатые члены экипажа остались сыты.
|
||||
bounty-description-microwave-machine-board = Мистер Хихстер решил, что будет забавно позасовывать металлические вилки во все кухонные микроволновки. Помогите нам отремонтировать их, пока повара не принялись готовить клоунбургеры.
|
||||
bounty-description-lasergun = Караван утилизаторов запрашивает большую партию лазерного оружия для уничтожения улья ксеноморфов.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
comp-self-unremovable-clothing = Это нельзя снять без посторонней помощи.
|
||||
|
|
@ -25,12 +25,18 @@ figurines-musician-2 = Never gonna let you down!
|
|||
|
||||
figurines-captain-1 = Экипаж, диск от ядерной бомбы надежно спрятан у меня в заднице.
|
||||
figurines-captain-2 = Слава НТ!
|
||||
figurines-captain-3 = Ядерный диск в безопасности. Где? Где-то.
|
||||
figurines-hos-1 = Космический закон? Что?
|
||||
figurines-hos-2 = Пристрелил клоуна.
|
||||
figurines-hos-3 = Да, я выстрелил в клоуна. Нет, я не жалею об этом.
|
||||
figurines-warden-1 = Казните его за взлом!
|
||||
figurines-warden-2 = Навсегда накажем этого урода за оскорбление!
|
||||
figurines-warden-3 = Мы абсолютно честно обращаемся со всеми и НЕ злоупотребляем нашими заключенными.
|
||||
figurines-detective-1 = Это сделал дворецкий.
|
||||
figurines-detective-2 = Мне нужно немного виски после этого.
|
||||
figurines-security-1 = Я и есть закон!
|
||||
figurines-security-2 = Вы нарушили статью 1984.
|
||||
figurines-security-3 = Когда мне становится скучно, я использую клоуна для стрельбы.
|
||||
figurines-lawyer-1 = Лучше позвоните Солу!
|
||||
figurines-lawyer-2 = Протестую!
|
||||
figurines-cargotech-1 = ДРАКОН НА АТЗ!
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ ui-options-scale-175 = 175%
|
|||
ui-options-scale-200 = 200%
|
||||
ui-options-hud-theme = Тема HUD:
|
||||
ui-options-hud-theme-default = По умолчанию
|
||||
ui-options-hud-layout-default = Обычный
|
||||
ui-options-hud-layout-separated = Раздельный
|
||||
ui-options-hud-theme-plasmafire = Плазма
|
||||
ui-options-hud-theme-slimecore = Слаймкор
|
||||
ui-options-hud-theme-clockwork = Механизм
|
||||
|
|
@ -149,6 +151,9 @@ ui-options-static-storage-ui = Закрепить интерфейс храни
|
|||
ui-options-function-smart-equip-backpack = Умная экипировка в рюкзак
|
||||
ui-options-function-smart-equip-belt = Умная экипировка на пояс
|
||||
ui-options-function-open-backpack = Открыть рюкзак
|
||||
ui-options-function-rotate-object-clockwise = Вращать по часовой стрелке
|
||||
ui-options-function-rotate-object-counterclockwise = Вращать против часовой стрелки
|
||||
ui-options-function-flip-object = Повернуть
|
||||
ui-options-function-open-belt = Открыть пояс
|
||||
ui-options-function-throw-item-in-hand = Бросить предмет
|
||||
ui-options-function-try-pull-object = Тянуть объект
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
all-at-once-title = Всё и сразу
|
||||
all-at-once-description = Это просто не ваш день...
|
||||
aller-at-once-title = Всё и вся сразу
|
||||
aller-at-once-description = Вы теперь серьёзно накосячили. Теперь вы *действительно* накосячили.
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
survival-title = Выживание
|
||||
survival-description = Внутренние угрозы отсутствуют, но как долго станция сможет продержаться в обстановке всё более разрушительных и частых событий?
|
||||
kessler-syndrome-title = Синдром Кесслера
|
||||
kessler-syndrome-description = Нет внутренних угроз, но станция быстро попадает в пояс метеоров!
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
zombie-title = Зомби
|
||||
zombie-description = На станции появились ожившие мертвецы! Работайте сообща с другими членами экипажа, чтобы пережить эпидемию и защитить станцию.
|
||||
zombieteors-title = Зомбитеоры
|
||||
zombieteors-description = Нежить была выпущена на станцию на фоне катастрофического метеоритного дождя! Работайте вместе с вашим экипажем и делайте все возможное, чтобы выжить!
|
||||
zombie-not-enough-ready-players = Недостаточно игроков готовы к игре! { $readyPlayersCount } игроков из необходимых { $minimumPlayers } готовы. Нельзя запустить пресет Зомби.
|
||||
zombie-no-one-ready = Нет готовых игроков! Нельзя запустить пресет Зомби.
|
||||
zombie-patientzero-role-greeting = Вы — нулевой пациент. Снарядитесь и подготовьтесь к своему превращению. Ваша цель - захватить станцию, заразив при этом как можно больше членов экипажа.
|
||||
|
|
|
|||
|
|
@ -351,3 +351,13 @@ reagent-effect-guidebook-plant-robust-harvest =
|
|||
[1] Повышает
|
||||
*[other] повышают
|
||||
} потенцию растения путём { $increase } до максимума в { $limit }. Приводит к тому, что растение теряет свои семена, когда потенция достигает { $seedlesstreshold }. Попытка повысить потенцию свыше { $limit } может вызвать снижение урожайности с вероятностью 10%.
|
||||
reagent-effect-guidebook-plant-seeds-add =
|
||||
{ $chance ->
|
||||
[1] Restores the
|
||||
*[other] restore the
|
||||
} seeds of the plant
|
||||
reagent-effect-guidebook-plant-seeds-remove =
|
||||
{ $chance ->
|
||||
[1] Removes the
|
||||
*[other] remove the
|
||||
} seeds of the plant
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
plant-attribute-growth = возраст
|
||||
plant-attribute-water = уровень воды
|
||||
plant-attribute-potency = потенция
|
||||
plant-attribute-weeds = уровень сорняков
|
||||
plant-attribute-toxins = уровень токсинов
|
||||
plant-attribute-nutrition = уровень питательных веществ
|
||||
|
|
|
|||
|
|
@ -1,7 +1,79 @@
|
|||
food-sequence-no-space = Вы не можете положить больше!
|
||||
food-sequence-standart-gen = { $prefix }{ $content }{ $suffix }
|
||||
food-sequence-content-chicken = курица
|
||||
food-sequence-content-duck = утка
|
||||
food-sequence-content-crab = крабы
|
||||
food-sequence-content-dragon = дракон
|
||||
food-sequence-content-snake = змея
|
||||
food-sequence-content-xeno = ксеноморф
|
||||
food-sequence-content-rouny = роуни
|
||||
food-sequence-content-tomato = помидор
|
||||
food-sequence-content-salami = салями
|
||||
food-sequence-content-slime = слизь
|
||||
food-sequence-content-clown = клоун
|
||||
food-sequence-content-pea = горох
|
||||
food-sequence-content-bungo = бунго
|
||||
food-sequence-content-banana = банан
|
||||
food-sequence-content-mimana = мимана
|
||||
food-sequence-content-lemon = лимон
|
||||
food-sequence-content-lemoon = лему́н
|
||||
food-sequence-content-lime = лайм
|
||||
food-sequence-content-orange = апельсин
|
||||
food-sequence-content-potato = картофель
|
||||
food-sequence-content-apple = яблоко
|
||||
food-sequence-content-cocoa = какао
|
||||
food-sequence-content-corn = кукуруза
|
||||
food-sequence-content-chili = чили
|
||||
food-sequence-content-chilly = охлаждённый
|
||||
food-sequence-content-mushroom = грибы
|
||||
food-sequence-content-aloe = алоэ
|
||||
food-sequence-content-poppy = мак
|
||||
food-sequence-content-lily = лилия
|
||||
food-sequence-content-soy = соя
|
||||
food-sequence-content-cheese = сыр
|
||||
food-sequence-content-chevre = chèvre
|
||||
food-sequence-content-tofu = тофу
|
||||
food-sequence-content-brain = мозг
|
||||
food-sequence-content-tongue = язык
|
||||
food-sequence-content-ears = ухо
|
||||
food-sequence-content-stomach = желудок
|
||||
food-sequence-content-liver = печень
|
||||
food-sequence-content-clump = ком
|
||||
food-sequence-content-raw-meat = сырое мясо
|
||||
food-sequence-content-meat = мясо
|
||||
food-sequence-content-carp = карп
|
||||
food-sequence-content-bear = медвежатина
|
||||
food-sequence-content-penguin = мясо пингвина
|
||||
food-sequence-content-corgi = мясо корги
|
||||
food-sequence-content-goliath = мясо голиафа
|
||||
food-sequence-content-rat = крыса
|
||||
food-sequence-content-lizard = мясо ящерицы
|
||||
food-sequence-content-plant = растительное мясо
|
||||
food-sequence-content-rotten = гнилое мясо
|
||||
food-sequence-content-spider = паучьи лапки
|
||||
food-sequence-content-carrot = морковь
|
||||
food-sequence-content-cabbage = капуста
|
||||
food-sequence-content-garlic = чеснок
|
||||
food-sequence-content-pineapple = ананас
|
||||
food-sequence-content-onion = лук
|
||||
food-sequence-content-ambrosia = амброзия
|
||||
food-sequence-content-galaxy = галактический чертополох
|
||||
food-sequence-content-glasstle = гластл
|
||||
food-sequence-content-gatfruit = гатфрут
|
||||
food-sequence-content-koibean = койбина
|
||||
food-sequence-content-watermelon = арбуз
|
||||
food-sequence-content-cannabis = каннабис
|
||||
food-sequence-content-rainbow-cannabis = радужный каннабис
|
||||
food-sequence-content-tobacco = табак
|
||||
food-sequence-content-hamster = хомяк
|
||||
food-sequence-content-suppermatter = супермасса
|
||||
food-sequence-content-capfruit = капфрут
|
||||
food-sequence-content-berries = ягоды
|
||||
food-sequence-content-spacemans-trumpet = космический духовой инструмент
|
||||
#Burger
|
||||
food-sequence-burger-gen = { $content }бургер
|
||||
food-sequence-burger-suffix = бургер
|
||||
food-sequence-burger-content-meat = мясно
|
||||
food-sequence-burger-content-raw-meat = сырой
|
||||
food-sequence-burger-content-carp = карпо
|
||||
food-sequence-burger-content-bear = медведь
|
||||
|
|
@ -21,6 +93,7 @@ food-sequence-burger-content-clump = комок
|
|||
food-sequence-burger-content-snake = змея
|
||||
food-sequence-burger-content-xeno = ксено
|
||||
food-sequence-burger-content-tomato = томат
|
||||
food-sequence-burger-content-capfruit = кап
|
||||
food-sequence-burger-content-salami = салями
|
||||
food-sequence-burger-content-clown = клоун
|
||||
food-sequence-burger-content-slime = слайм
|
||||
|
|
@ -28,8 +101,14 @@ food-sequence-burger-content-pea = пеа
|
|||
food-sequence-burger-content-banana = банан
|
||||
food-sequence-burger-content-mimana = мимана
|
||||
food-sequence-burger-content-carrot = морковно
|
||||
food-sequence-burger-content-suppermatter = суппер
|
||||
food-sequence-burger-content-hamster = хомя
|
||||
food-sequence-burger-content-berries = ягодно
|
||||
food-sequence-burger-content-spacemans-trumpet = космострубко
|
||||
food-sequence-burger-content-cabbage = капуста
|
||||
food-sequence-taco-gen = тако с { $content }
|
||||
food-sequence-burger-content-garlic = чесночно
|
||||
food-sequence-skewer-gen = { $content } кебаб
|
||||
food-sequence-burger-content-lemon = лемон
|
||||
food-sequence-burger-content-orange = апельсино
|
||||
food-sequence-burger-content-pineapple = ананасо
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
power-radiation-collector-gas-tank-missing = Выемка для баллона с плазмой [color=darkred]пустует[/color].
|
||||
power-radiation-collector-gas-tank-present = Выемка для баллона с плазмой [color=darkgreen]заполнена[/color] и индикатор баллона находится на отметке [color={$fullness ->
|
||||
*[0]red]пусто
|
||||
[1]red]мало
|
||||
[2]yellow]заполнено наполовину
|
||||
[3]lime]заполнено
|
||||
}[/color].
|
||||
power-radiation-collector-enabled = Находится в режиме [color={$state ->
|
||||
[true] darkgreen]вкл
|
||||
*[false] darkred]выкл
|
||||
}[/color].
|
||||
power-radiation-collector-gas-tank-present =
|
||||
Выемка для баллона с плазмой [color=darkgreen]заполнена[/color] и индикатор баллона находится на отметке [color={ $fullness ->
|
||||
*[0] red]пусто
|
||||
[1] red]мало
|
||||
[2] yellow]заполнено наполовину
|
||||
[3] lime]заполнено
|
||||
}[/color].
|
||||
power-radiation-collector-enabled =
|
||||
Находится в режиме [color={ $state ->
|
||||
[true] darkgreen]вкл
|
||||
*[false] darkred]выкл
|
||||
}[/color].
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ loadout-group-passenger-mask = Пассажир, маска
|
|||
loadout-group-passenger-gloves = Пассажир, перчатки
|
||||
loadout-group-passenger-outerclothing = Пассажир, верхняя одежда
|
||||
loadout-group-passenger-shoes = Пассажир, обувь
|
||||
loadout-group-passenger-neck = Пассажир, шея
|
||||
loadout-group-bartender-head = Бармен, голова
|
||||
loadout-group-bartender-jumpsuit = Бармен, комбинезон
|
||||
loadout-group-bartender-outerclothing = Бармен, верхняя одежда
|
||||
|
|
@ -133,6 +134,7 @@ loadout-group-detective-neck = Детектив, шея
|
|||
loadout-group-detective-jumpsuit = Детектив, комбинезон
|
||||
loadout-group-detective-outerclothing = Детектив, верхняя одежда
|
||||
loadout-group-security-cadet-jumpsuit = Кадет СБ, комбинезон
|
||||
loadout-group-security-star = Звезда СБ
|
||||
# Medical
|
||||
loadout-group-medical-gloves = Медицинский отдел, перчатки
|
||||
loadout-group-medical-mask = Медицинский отдел, маска
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ marking-used = { $marking-name }
|
|||
marking-used-forced = { $marking-name } (Принудительно)
|
||||
marking-slot-add = Добавить
|
||||
marking-slot-remove = Удалить
|
||||
marking-slot = Слот { $number }
|
||||
|
||||
# Categories
|
||||
|
||||
|
|
|
|||
|
|
@ -14,3 +14,5 @@ reagent-name-ammonia = аммиак
|
|||
reagent-desc-ammonia = Эффективное удобрение, дающее растениям питательные вещества.
|
||||
reagent-name-diethylamine = диэтиламин
|
||||
reagent-desc-diethylamine = Очень мощное удобрение, насыщает растения питательными веществами, уничтожает вредителей, а иногда даже ускоряет рост.
|
||||
reagent-name-sedin = седин
|
||||
reagent-desc-sedin = Модифицированная версия диэтилового аммиака, которая может восстанавливать семена на растениях за счет силы действия.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ research-technology-biochemical-stasis = Биохимический стазис
|
|||
research-technology-mechanized-treatment = Механизированная подготовка
|
||||
research-technology-bluespace-chemistry = Блюспейс-химия
|
||||
research-technology-salvage-weapons = Утилизаторское оружие
|
||||
research-technology-special-means = Специальные средства
|
||||
research-technology-draconic-munitions = Драконьи боеприпасы
|
||||
research-technology-nonlethal-ammunition = Не летальная аммуниция
|
||||
research-technology-uranium-munitions = Урановые боеприпасы
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ dungeon-config-proto-ClusterAsteroid = Астероидный кластер
|
|||
dungeon-config-proto-SpindlyAsteroid = Астероидная спираль
|
||||
dungeon-config-proto-SwissCheeseAsteroid = Фрагменты астероидов
|
||||
# Wrecks
|
||||
salvage-map-wreck = Спасательный обломок
|
||||
salvage-map-wreck-desc-size = Размер:
|
||||
salvage-map-wreck-size-small = [color=lime]Маленький[/color]
|
||||
salvage-map-wreck-size-medium = [color=cornflowerblue]Средний[/color]
|
||||
salvage-map-wreck-size-large = [color=orchid]Большой[/color]
|
||||
# Wrecks
|
||||
salvage-map-proto-Small1 = Инженерное хранилище
|
||||
salvage-map-proto-Small2 = Игровой уголок
|
||||
salvage-map-proto-Small3 = Прачечная
|
||||
|
|
|
|||
|
|
@ -122,3 +122,5 @@ seeds-cotton-name = хлопок
|
|||
seeds-cotton-display-name = куст хлопка
|
||||
seeds-pyrotton-name = пирохлопок
|
||||
seeds-pyrotton-display-name = куст пирохлопка
|
||||
seeds-capfruit-name = капфрут
|
||||
seeds-capfruit-display-name = дерево капфрута
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ent-RandomIngredient = спавнер случайных ингредиентов
|
||||
.suffix = Не растительные
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ent-RandomMeat = спавнер случайного мяса
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
ent-SalvageMaterialCrateSpawner = спавнер обломок ящик материалов
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageCanisterSpawner = спавнер обломок канистра
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageLootSpawner = спавнер обломок добыча
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageMobSpawner = спавнер обломок моб
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
ent-SalvageSpawnerScrapCommon = Спавнер металлолома
|
||||
.suffix = Обычный, 50%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerScrapCommon75 = { ent-SalvageSpawnerScrapCommon }
|
||||
.suffix = Обычный, 75%
|
||||
.desc = { ent-SalvageSpawnerScrapCommon.desc }
|
||||
ent-SalvageSpawnerScrapValuable = Спавнер металлолома
|
||||
.suffix = Ценный, 50%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerScrapValuable75 = { ent-SalvageSpawnerScrapValuable }
|
||||
.suffix = Ценный, 75%
|
||||
.desc = { ent-SalvageSpawnerScrapValuable.desc }
|
||||
ent-SalvageSpawnerTreasure = Спавнер сокровищ
|
||||
.suffix = Обычный, 75%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerTreasureValuable = Спавнер сокровищ
|
||||
.suffix = Ценный, 75%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerEquipment = Спавнер снаряжения
|
||||
.suffix = Обычный, 75%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerEquipmentValuable = Спавнер снаряжения
|
||||
.suffix = Ценный, 75%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageCanisterSpawner = спавнер обломок канистра
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerMobMagnet = Спавнер мобов-магнитов
|
||||
.suffix = 50%
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
ent-SalvageSpawnerMobMagnet75 = { ent-SalvageSpawnerMobMagnet }
|
||||
.suffix = 75%
|
||||
.desc = { ent-SalvageSpawnerMobMagnet.desc }
|
||||
ent-SalvageSpawnerMobMagnet100 = { ent-SalvageSpawnerMobMagnet }
|
||||
.suffix = 100%
|
||||
.desc = { ent-SalvageSpawnerMobMagnet.desc }
|
||||
ent-SalvageSpawnerMobShark = Спавнер акуломиннов
|
||||
.desc = { ent-MarkerBase.desc }
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
ent-TreasureDatadiskEncrypted = зашифрованный дата-диск
|
||||
.desc = Старый дата-диск. Кто-то более сведущий, чем вы, мог бы его расшифровать и узнать что-то интересное. Всё, что вы можете сделать — это продать его.
|
||||
ent-TreasureHardDiskDrive = жёсткий диск
|
||||
.desc = Дисковый накопитель, предназначенный для старых земных технологий, примерно 1980 года. Возможно, найдётся какой-то коллекционер, который захочет заполучить это.
|
||||
ent-TreasureFlopDiskDrive = дисковод для гибких дисков
|
||||
.desc = Дисковод для чтения информации с гибких дисков. Жаль, что в наши дни на них хранятся только операции ядерных бомб.
|
||||
ent-TreasureCDDrive = CD-дисковод
|
||||
.desc = Устройство для чтения данных с компакт-дисков. В наше время это не самое полезное устройство, если вы, конечно, не собираетесь куда-то лететь.
|
||||
ent-TreasureCPUSupercharged = сверхзаряженный процессор
|
||||
.desc = Какая-то супер-инопланетная космотехника. Жаль, что все компьютеры уже оснащены процессорами.
|
||||
ent-TreasureSampleTube = пробирка с образцом
|
||||
.desc = Стеклянная пробирка с какой-то субстанцией. Выглядит хрупкой.
|
||||
ent-TreasureCoinIron = монета
|
||||
.desc = Плоский кусок металла. Если бы она всё ещё была в обращении, инфляция сделала бы её бесполезной. Но так как это не так, она стоит достаточно, чтобы хранить её в книге.
|
||||
ent-TreasureCoinSilver = { ent-TreasureCoinIron }
|
||||
.desc = { ent-TreasureCoinIron.desc }
|
||||
ent-TreasureCoinGold = { ent-TreasureCoinIron }
|
||||
.desc = { ent-TreasureCoinIron.desc }
|
||||
ent-TreasureCoinAdamantine = { ent-TreasureCoinIron }
|
||||
.desc = { ent-TreasureCoinIron.desc }
|
||||
ent-TreasureCoinDiamond = { ent-TreasureCoinIron }
|
||||
.desc = { ent-TreasureCoinIron.desc }
|
||||
|
|
@ -31,5 +31,7 @@ trait-liar-name = Патологический лжец
|
|||
trait-liar-desc = Вы с трудом заставляете себя говорить правду. Иногда вы всё равно лжёте.
|
||||
trait-cowboy-name = Ковбойский акцент
|
||||
trait-cowboy-desc = Вы говорите с заметным ковбойским акцентом!
|
||||
trait-german-name = Немецкий акцент
|
||||
trait-german-desc = Похоже, вы родом из космической Германии.
|
||||
trait-italian-name = Итальянский акцент
|
||||
trait-italian-desc = Мамма миа! Похоже, вы жили в Космической Италии!
|
||||
|
|
|
|||
|
|
@ -1,3 +1,88 @@
|
|||
- type: entity
|
||||
id: ActionInfect
|
||||
name: action-disease-infect-name
|
||||
description: action-disease-infect-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
useDelay: 1
|
||||
itemIconStyle: BigAction
|
||||
whitelist:
|
||||
components:
|
||||
- HumanoidAppearance
|
||||
canTargetSelf: false
|
||||
interactOnMiss: false
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: action
|
||||
event: !type:InfectEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseShop
|
||||
name: action-disease-shop-name
|
||||
description: action-disease-shop-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: shop
|
||||
event: !type:DiseaseShopActionEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseBaseChance
|
||||
name: listing-disease-base-chance-name
|
||||
description: listing-disease-base-chance-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: baseChance
|
||||
event: !type:DiseaseAddBaseChanceEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseInfectChance
|
||||
name: listing-disease-infect-chance-name
|
||||
description: listing-disease-infect-chance-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: coughChance
|
||||
event: !type:DiseaseAddCoughChanceEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseShield
|
||||
name: listing-disease-shield-name
|
||||
description: listing-disease-shield-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: shield
|
||||
event: !type:DiseaseAddShieldEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseLethal
|
||||
name: listing-disease-lethal-name
|
||||
description: listing-disease-lethal-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: lethal
|
||||
event: !type:DiseaseAddLethalEvent
|
||||
|
||||
#Store
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseCough
|
||||
name: listing-disease-cough-name
|
||||
|
|
@ -148,3 +233,4 @@
|
|||
symptom: "Insult"
|
||||
minLevel: 2
|
||||
|
||||
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
- type: entity
|
||||
id: ActionInfect
|
||||
name: action-disease-infect-name
|
||||
description: action-disease-infect-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: EntityTargetAction
|
||||
useDelay: 1
|
||||
itemIconStyle: BigAction
|
||||
whitelist:
|
||||
components:
|
||||
- HumanoidAppearance
|
||||
canTargetSelf: false
|
||||
interactOnMiss: false
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: action
|
||||
event: !type:InfectEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseShop
|
||||
name: action-disease-shop-name
|
||||
description: action-disease-shop-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: shop
|
||||
event: !type:DiseaseShopActionEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseBaseChance
|
||||
name: listing-disease-base-chance-name
|
||||
description: listing-disease-base-chance-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: baseChance
|
||||
event: !type:DiseaseAddBaseChanceEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseInfectChance
|
||||
name: listing-disease-infect-chance-name
|
||||
description: listing-disease-infect-chance-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: coughChance
|
||||
event: !type:DiseaseAddCoughChanceEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseShield
|
||||
name: listing-disease-shield-name
|
||||
description: listing-disease-shield-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: shield
|
||||
event: !type:DiseaseAddShieldEvent
|
||||
|
||||
- type: entity
|
||||
id: ActionDiseaseLethal
|
||||
name: listing-disease-lethal-name
|
||||
description: listing-disease-lethal-description
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: InstantAction
|
||||
itemIconStyle: NoItem
|
||||
icon:
|
||||
sprite: _Sunrise/Actions/disease.rsi
|
||||
state: lethal
|
||||
event: !type:DiseaseAddLethalEvent
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
- type: emote
|
||||
id: Vomit
|
||||
name: Тошнота
|
||||
category: Vocal
|
||||
chatMessages: [тошнитыы]
|
||||
|
||||
- type: emote
|
||||
id: Headache
|
||||
name: Головная боль
|
||||
category: Vocal
|
||||
chatMessages: [голова болитыы]
|
||||
|
||||
- type: emote
|
||||
id: Insult
|
||||
name: Инсульт
|
||||
category: Vocal
|
||||
chatMessages: [инсультыы]
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
- type: storeCategory
|
||||
id: DiseaseInfectCategory
|
||||
name: shop-disease-category-infect
|
||||
|
||||
- type: storeCategory
|
||||
id: DiseaseSymptomsCategory
|
||||
name: shop-disease-category-symptoms
|
||||
|
||||
- type: storeCategory
|
||||
id: DiseaseEvolutionCategory
|
||||
name: shop-disease-category-evolution
|
||||
|
||||
|
||||
- type: currency
|
||||
id: DiseasePoints
|
||||
displayName: shop-disease-currency
|
||||
canWithdraw: false
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: DiseaseSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
minimumPlayers: 40
|
||||
weight: 10
|
||||
maxOccurrences: 1
|
||||
duration: 1
|
||||
earliestStart: 30
|
||||
reoccurrenceDelay: 20
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: MobDisease
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
- type: entity
|
||||
id: DiseaseRule
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: DiseaseRule
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: EvilTwinSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
minimumPlayers: 2
|
||||
weight: 10
|
||||
duration: 1
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: SpawnPointEvilTwin
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
- type: entity
|
||||
id: EvilTwin
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: EvilTwinRule
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: FugitiveSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
weight: 10
|
||||
duration: 1
|
||||
earliestStart: 30
|
||||
minimumPlayers: 10
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: SpawnPointGhostFugitive
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
- type: entity
|
||||
id: Fugitive
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: FugitiveRule
|
||||
39
Resources/Prototypes/_Sunrise/GameRules/events.yml
Normal file
39
Resources/Prototypes/_Sunrise/GameRules/events.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: DiseaseSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
minimumPlayers: 40
|
||||
weight: 10
|
||||
maxOccurrences: 1
|
||||
duration: 1
|
||||
earliestStart: 30
|
||||
reoccurrenceDelay: 20
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: MobDisease
|
||||
|
||||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: FugitiveSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
weight: 10
|
||||
duration: 1
|
||||
earliestStart: 30
|
||||
minimumPlayers: 10
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: SpawnPointGhostFugitive
|
||||
|
||||
- type: entity
|
||||
parent: BaseGameRule
|
||||
id: EvilTwinSpawn
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: StationEvent
|
||||
minimumPlayers: 2
|
||||
weight: 10
|
||||
duration: 1
|
||||
- type: LateJoinSpawnRule
|
||||
prototype: SpawnPointEvilTwin
|
||||
20
Resources/Prototypes/_Sunrise/GameRules/roundstart.yml
Normal file
20
Resources/Prototypes/_Sunrise/GameRules/roundstart.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
- type: entity
|
||||
id: DiseaseRule
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: DiseaseRule
|
||||
|
||||
- type: entity
|
||||
id: Fugitive
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: FugitiveRule
|
||||
|
||||
- type: entity
|
||||
id: EvilTwin
|
||||
parent: BaseGameRule
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: EvilTwinRule
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
- type: loadout
|
||||
id: BlueShieldDuffel
|
||||
equipment:
|
||||
jumpsuit: ClothingBackpackDuffelBlueShield
|
||||
back: ClothingBackpackDuffelBlueShield
|
||||
|
||||
- type: loadout
|
||||
id: BlueShieldJumpskirt
|
||||
|
|
|
|||
|
|
@ -92,3 +92,21 @@
|
|||
id: SecurityThings
|
||||
name: Служба Безопасности
|
||||
priority: 5
|
||||
|
||||
- type: storeCategory
|
||||
id: DiseaseInfectCategory
|
||||
name: shop-disease-category-infect
|
||||
|
||||
- type: storeCategory
|
||||
id: DiseaseSymptomsCategory
|
||||
name: shop-disease-category-symptoms
|
||||
|
||||
- type: storeCategory
|
||||
id: DiseaseEvolutionCategory
|
||||
name: shop-disease-category-evolution
|
||||
|
||||
|
||||
- type: currency
|
||||
id: DiseasePoints
|
||||
displayName: shop-disease-currency
|
||||
canWithdraw: false
|
||||
|
|
|
|||
|
|
@ -189,3 +189,21 @@
|
|||
- хряк.
|
||||
- хряк
|
||||
- хряк!
|
||||
|
||||
- type: emote
|
||||
id: Vomit
|
||||
name: Тошнота
|
||||
category: Vocal
|
||||
chatMessages: [тошнитыы]
|
||||
|
||||
- type: emote
|
||||
id: Headache
|
||||
name: Головная боль
|
||||
category: Vocal
|
||||
chatMessages: [голова болитыы]
|
||||
|
||||
- type: emote
|
||||
id: Insult
|
||||
name: Инсульт
|
||||
category: Vocal
|
||||
chatMessages: [инсультыы]
|
||||
|
|
|
|||
71
Tools/_sunrise/Report.txt
Normal file
71
Tools/_sunrise/Report.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
Проверка локализации:
|
||||
Не переведённая строка "accent-german-words-replace-1" в "accent\german.ftl", строка 2: accent-german-words-replace-1 = ja
|
||||
Не переведённая строка "accent-german-words-replace-2" в "accent\german.ftl", строка 4: accent-german-words-replace-2 = nein
|
||||
Не переведённая строка "accent-german-words-replace-3" в "accent\german.ftl", строка 6: accent-german-words-replace-3 = Das
|
||||
Не переведённая строка "accent-german-words-replace-4" в "accent\german.ftl", строка 8: accent-german-words-replace-4 = scheisse
|
||||
Не переведённая строка "accent-german-words-replace-5" в "accent\german.ftl", строка 10: accent-german-words-replace-5 = wurst
|
||||
Не переведённая строка "accent-german-words-replace-6" в "accent\german.ftl", строка 12: accent-german-words-replace-6 = würste
|
||||
Не переведённая строка "accent-german-words-replace-7" в "accent\german.ftl", строка 14: accent-german-words-replace-7 = mann
|
||||
Не переведённая строка "accent-german-words-replace-8" в "accent\german.ftl", строка 16: accent-german-words-replace-8 = männer
|
||||
Не переведённая строка "accent-german-words-9-2" в "accent\german.ftl", строка 18: accent-german-words-9-2 = lady
|
||||
Не переведённая строка "accent-german-words-replace-9" в "accent\german.ftl", строка 19: accent-german-words-replace-9 = frau
|
||||
Не переведённая строка "accent-german-words-10-2" в "accent\german.ftl", строка 21: accent-german-words-10-2 = ladies
|
||||
Не переведённая строка "accent-german-words-replace-10" в "accent\german.ftl", строка 22: accent-german-words-replace-10 = frauen
|
||||
Не переведённая строка "accent-german-words-replace-11" в "accent\german.ftl", строка 24: accent-german-words-replace-11 = herr
|
||||
Не переведённая строка "accent-german-words-replace-12" в "accent\german.ftl", строка 26: accent-german-words-replace-12 = herren
|
||||
Не переведённая строка "accent-german-words-replace-13" в "accent\german.ftl", строка 28: accent-german-words-replace-13 = mein gott
|
||||
Не переведённая строка "accent-german-words-replace-14" в "accent\german.ftl", строка 30: accent-german-words-replace-14 = mein
|
||||
Не переведённая строка "accent-german-words-replace-15" в "accent\german.ftl", строка 32: accent-german-words-replace-15 = hier
|
||||
Не переведённая строка "accent-german-words-replace-16" в "accent\german.ftl", строка 34: accent-german-words-replace-16 = dummkopf
|
||||
Не переведённая строка "accent-german-words-replace-17" в "accent\german.ftl", строка 36: accent-german-words-replace-17 = dummköpfe
|
||||
Не переведённая строка "accent-german-words-replace-18" в "accent\german.ftl", строка 38: accent-german-words-replace-18 = schmetterling
|
||||
Не переведённая строка "accent-german-words-replace-19" в "accent\german.ftl", строка 40: accent-german-words-replace-19 = maschine
|
||||
Не переведённая строка "accent-german-words-replace-19-2" в "accent\german.ftl", строка 41: accent-german-words-replace-19-2 = auto
|
||||
Не переведённая строка "accent-german-words-replace-20" в "accent\german.ftl", строка 43: accent-german-words-replace-20 = maschinen
|
||||
Не переведённая строка "accent-german-words-replace-21" в "accent\german.ftl", строка 45: accent-german-words-replace-21 = achtung
|
||||
Не переведённая строка "accent-german-words-replace-22" в "accent\german.ftl", строка 47: accent-german-words-replace-22 = musik
|
||||
Не переведённая строка "accent-german-words-replace-23" в "accent\german.ftl", строка 49: accent-german-words-replace-23 = kapitän
|
||||
Не переведённая строка "accent-german-words-replace-24" в "accent\german.ftl", строка 51: accent-german-words-replace-24 = döner
|
||||
Не переведённая строка "accent-german-words-replace-25" в "accent\german.ftl", строка 53: accent-german-words-replace-25 = maus
|
||||
Не переведённая строка "accent-german-words-replace-26" в "accent\german.ftl", строка 55: accent-german-words-replace-26 = wat
|
||||
Не переведённая строка "accent-german-words-replace-27" в "accent\german.ftl", строка 57: accent-german-words-replace-27 = dankeschön
|
||||
Не переведённая строка "accent-german-words-replace-28" в "accent\german.ftl", строка 59: accent-german-words-replace-28 = danke
|
||||
Не переведённая строка "accent-german-words-replace-29" в "accent\german.ftl", строка 61: accent-german-words-replace-29 = gesundheit
|
||||
Не переведённая строка "accent-german-words-replace-30" в "accent\german.ftl", строка 63: accent-german-words-replace-30 = flammenwerfer
|
||||
Не переведённая строка "accent-german-words-replace-31" в "accent\german.ftl", строка 65: accent-german-words-replace-31 = poltergeist
|
||||
Не переведённая строка "accent-german-words-32-2" в "accent\german.ftl", строка 67: accent-german-words-32-2 = cabbage
|
||||
Не переведённая строка "accent-german-words-replace-32" в "accent\german.ftl", строка 68: accent-german-words-replace-32 = kraut
|
||||
Не переведённая строка "accent-german-words-replace-33" в "accent\german.ftl", строка 70: accent-german-words-replace-33 = wodka
|
||||
Не переведённая строка "accent-german-words-replace-34" в "accent\german.ftl", строка 72: accent-german-words-replace-34 = rucksack
|
||||
Не переведённая строка "accent-german-words-replace-35" в "accent\german.ftl", строка 74: accent-german-words-replace-35 = medizin
|
||||
Не переведённая строка "accent-german-words-replace-36" в "accent\german.ftl", строка 76: accent-german-words-replace-36 = akzent
|
||||
Не переведённая строка "accent-german-words-replace-37" в "accent\german.ftl", строка 78: accent-german-words-replace-37 = anomalie
|
||||
Не переведённая строка "accent-german-words-38-2" в "accent\german.ftl", строка 80: accent-german-words-38-2 = artefact
|
||||
Не переведённая строка "accent-german-words-replace-38" в "accent\german.ftl", строка 81: accent-german-words-replace-38 = artefakt
|
||||
Не переведённая строка "accent-german-words-replace-39" в "accent\german.ftl", строка 83: accent-german-words-replace-39 = dumm
|
||||
Не переведённая строка "accent-german-words-replace-40" в "accent\german.ftl", строка 85: accent-german-words-replace-40 = doof
|
||||
Не переведённая строка "accent-german-words-replace-41" в "accent\german.ftl", строка 88: accent-german-words-replace-41 = wunderbar
|
||||
Не переведённая строка "accent-german-words-replace-42" в "accent\german.ftl", строка 90: accent-german-words-replace-42 = warnung
|
||||
Не переведённая строка "accent-german-words-replace-43" в "accent\german.ftl", строка 92: accent-german-words-replace-43 = warnungen
|
||||
Не переведённая строка "accent-german-words-replace-44" в "accent\german.ftl", строка 94: accent-german-words-replace-44 = und
|
||||
Не переведённая строка "accent-german-words-replace-45" в "accent\german.ftl", строка 96: accent-german-words-replace-45 = karpfen
|
||||
Не переведённая строка "accent-german-words-replace-46" в "accent\german.ftl", строка 98: accent-german-words-replace-46 = kommandant
|
||||
Не переведённая строка "accent-german-words-replace-47" в "accent\german.ftl", строка 100: accent-german-words-replace-47 = bier
|
||||
Не переведённая строка "accent-german-words-replace-48" в "accent\german.ftl", строка 102: accent-german-words-replace-48 = hallo
|
||||
Не переведённая строка "accent-german-words-replace-49" в "accent\german.ftl", строка 104: accent-german-words-replace-49 = guten Tag
|
||||
Не переведённая строка "accent-german-words-replace-50" в "accent\german.ftl", строка 106: accent-german-words-replace-50 = hilfe
|
||||
Не переведённая строка "accent-german-words-replace-51" в "accent\german.ftl", строка 108: accent-german-words-replace-51 = auf Wiedersehen
|
||||
Не переведённая строка "accent-german-words-replace-52" в "accent\german.ftl", строка 110: accent-german-words-replace-52 = tschüss
|
||||
Не переведённая строка "accent-german-words-replace-53" в "accent\german.ftl", строка 113: accent-german-words-replace-53 = tschau
|
||||
Не переведённая строка "accent-german-words-replace-54" в "accent\german.ftl", строка 115: accent-german-words-replace-54 = fantastisch
|
||||
Не переведённая строка "accent-german-words-replace-55" в "accent\german.ftl", строка 117: accent-german-words-replace-55 = doppelgänger
|
||||
Не переведённая строка "accent-german-words-replace-56" в "accent\german.ftl", строка 119: accent-german-words-replace-56 = verboten
|
||||
Не переведённая строка "accent-german-words-replace-57" в "accent\german.ftl", строка 122: accent-german-words-replace-57 = schnell
|
||||
Не переведённая строка "accent-german-words-replace-58" в "accent\german.ftl", строка 124: accent-german-words-replace-58 = krankenhaus
|
||||
Не переведённая строка "accent-german-words-replace-59" в "accent\german.ftl", строка 126: accent-german-words-replace-59 = tesla coil
|
||||
Не переведённая строка "accent-german-words-replace-60" в "accent\german.ftl", строка 128: accent-german-words-replace-60 = tesla coils
|
||||
Не переведённая строка "accent-german-words-61" в "accent\german.ftl", строка 129: accent-german-words-61 = teslaloose
|
||||
Не переведённая строка "accent-german-words-61-2" в "accent\german.ftl", строка 130: accent-german-words-61-2 = tesloose
|
||||
Не переведённая строка "accent-german-words-61-3" в "accent\german.ftl", строка 131: accent-german-words-61-3 = lightning ball
|
||||
Не переведённая строка "accent-german-words-replace-61" в "accent\german.ftl", строка 134: accent-german-words-replace-61 = kugelblitz
|
||||
1
Tools/_sunrise/__init__.py
Normal file
1
Tools/_sunrise/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from fluentformatter import FluentFile, FluentFormatter
|
||||
230
Tools/_sunrise/en_syntax_finder.py
Normal file
230
Tools/_sunrise/en_syntax_finder.py
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
def is_english(text):
|
||||
return bool(re.search('[a-zA-Z]', text))
|
||||
|
||||
def has_russian(text):
|
||||
return bool(re.search('[а-яА-Я]', text))
|
||||
|
||||
def remove_braces_content(text):
|
||||
text1 = re.sub(r'\{.*?\}', '', text)
|
||||
text2 = re.sub(r'\[.*?\]', '', text1)
|
||||
text3 = re.sub(r'\<.*?\>', '', text2)
|
||||
return text3
|
||||
|
||||
def contains_ignored_word(text, ignore_list):
|
||||
return any(ignored_word in text for ignored_word in ignore_list)
|
||||
|
||||
def check_translations(root_dir, report_file, ignore_list, ignore_files):
|
||||
root_dir_abs = os.path.abspath(root_dir)
|
||||
found_issues = False
|
||||
localization_issues = []
|
||||
|
||||
for dirpath, _, filenames in os.walk(root_dir):
|
||||
for filename in filenames:
|
||||
if filename.endswith('.ftl'):
|
||||
file_path = os.path.join(dirpath, filename)
|
||||
rel_path = os.path.relpath(file_path, root_dir_abs)
|
||||
|
||||
# Проверка, должен ли файл быть игнорирован
|
||||
if filename in ignore_files:
|
||||
print(f'Игнорирование файла: {filename}')
|
||||
continue
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as ftl_file:
|
||||
lines = ftl_file.readlines()
|
||||
for line_num, line in enumerate(lines, start=1):
|
||||
if '=' in line and not line.strip().startswith('#'):
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
value = remove_braces_content(value)
|
||||
|
||||
if not has_russian(value) and not contains_ignored_word(value, ignore_list):
|
||||
if key.endswith('.desc') or key.endswith('.suffix'):
|
||||
if is_english(value):
|
||||
localization_issues.append(f'Не переведённая строка "{key}" в "{rel_path}", строка {line_num}: {line.strip()}\n')
|
||||
elif is_english(value):
|
||||
localization_issues.append(f'Не переведённая строка "{key}" в "{rel_path}", строка {line_num}: {line.strip()}\n')
|
||||
|
||||
if localization_issues:
|
||||
found_issues = True
|
||||
with open(report_file, 'a', encoding='utf-8') as report:
|
||||
report.write("\nПроверка локализации:\n")
|
||||
report.writelines(localization_issues)
|
||||
|
||||
return found_issues
|
||||
|
||||
def check_prototypes_for_russian(root_dir, report_file):
|
||||
root_dir_abs = os.path.abspath(root_dir)
|
||||
found_issues = False
|
||||
prototype_issues = []
|
||||
|
||||
for dirpath, _, filenames in os.walk(root_dir):
|
||||
for filename in filenames:
|
||||
if filename.endswith('.yml') or filename.endswith('.yaml'):
|
||||
file_path = os.path.join(dirpath, filename)
|
||||
rel_path = os.path.relpath(file_path, root_dir_abs)
|
||||
with open(file_path, 'r', encoding='utf-8') as prototype_file:
|
||||
lines = prototype_file.readlines()
|
||||
for line_num, line in enumerate(lines, start=1):
|
||||
if has_russian(line):
|
||||
prototype_issues.append(f'Строка с русскими символами в "{rel_path}", строка {line_num}: {line.strip()}\n')
|
||||
|
||||
if prototype_issues:
|
||||
found_issues = True
|
||||
with open(report_file, 'a', encoding='utf-8') as report:
|
||||
report.write("\nПроверка прототипов:\n")
|
||||
report.writelines(prototype_issues)
|
||||
|
||||
return found_issues
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Список слов или фраз, которые нужно игнорировать
|
||||
ignore_list = [
|
||||
'EntityUid',
|
||||
'Zzz...',
|
||||
'playglobalsound',
|
||||
'variantize',
|
||||
'ID',
|
||||
'IP',
|
||||
'TileY',
|
||||
'TileX',
|
||||
'X', 'Y',
|
||||
'EI NATH',
|
||||
'>MFW',
|
||||
'Hello world!',
|
||||
'green', 'red',
|
||||
'purple',
|
||||
'yellow',
|
||||
'orange',
|
||||
'$count',
|
||||
'v1',
|
||||
'Nanotrasen',
|
||||
'float',
|
||||
'$open',
|
||||
'$rate',
|
||||
'LV-426',
|
||||
'LOOC',
|
||||
'OOC',
|
||||
'white',
|
||||
'blue',
|
||||
'grey',
|
||||
'#1b487e',
|
||||
'#B50F1D',
|
||||
'Never gonna let you down!',
|
||||
'Never gonna give you up!',
|
||||
'power_net_query',
|
||||
'zzz...',
|
||||
'GENDER',
|
||||
'$initialCount',
|
||||
'Github',
|
||||
'GitHub',
|
||||
'Space Station 14',
|
||||
'AHelp',
|
||||
'TARCOL MINTI ZHERI',
|
||||
"ONI'SOMA!",
|
||||
'AIE KHUSE EU',
|
||||
'Gray',
|
||||
'$enabled',
|
||||
'FOO-BAR-BAZ',
|
||||
'$state',
|
||||
'Float',
|
||||
'Integer',
|
||||
'left-4-zed',
|
||||
'plant-B-gone',
|
||||
'Pwr Game',
|
||||
'sol dry',
|
||||
'gray',
|
||||
'UITest2',
|
||||
'EVA',
|
||||
'MK58',
|
||||
'WT550',
|
||||
'N1984',
|
||||
'DEUS VULT',
|
||||
'AMS-42',
|
||||
'SAM-300',
|
||||
'AJ-100',
|
||||
'Deus vult! Ave maria!',
|
||||
'TR-263',
|
||||
'G-Man',
|
||||
'RGB',
|
||||
'Changelog',
|
||||
'M1 Garand',
|
||||
'BR-64',
|
||||
'MG-42',
|
||||
'MG-60',
|
||||
'RPD',
|
||||
"Monkin' Donuts",
|
||||
'P-90',
|
||||
'MP-38',
|
||||
'MP5',
|
||||
'MP7',
|
||||
'PPSH-41',
|
||||
'AKMS',
|
||||
'AK74-U',
|
||||
'G-36',
|
||||
'M-28',
|
||||
'AR-18',
|
||||
'M16A4',
|
||||
'STG 44',
|
||||
'ACP-14',
|
||||
'Glock-22',
|
||||
'GL-79',
|
||||
'M-41',
|
||||
'DEAD-BEEF',
|
||||
'UwU',
|
||||
'C-4',
|
||||
'Who ya gonna call?',
|
||||
'Ue No',
|
||||
'Carpe diem!',
|
||||
'Space-Up!',
|
||||
'Sun-kist',
|
||||
'Robust Softdrinks',
|
||||
'C-20r',
|
||||
'Donut Corp.',
|
||||
'LV426',
|
||||
'Kept ya waiting, huh?',
|
||||
'Getmore Chocolate Corp',
|
||||
'Discount Dan',
|
||||
'L6 SAW',
|
||||
'L6C ROW',
|
||||
'China Lake',
|
||||
'Plant-B-Gone',
|
||||
'Bon appétit!',
|
||||
'Bon ap-petite!',
|
||||
'Sol dry',
|
||||
'hover entity', # Спорная хуйня, проверить в игре
|
||||
'drag shadow', # Спорная хуйня, проверить в игре
|
||||
'dbg_rotation1',
|
||||
'dbg_rotation4',
|
||||
'dbg_rotationTex',
|
||||
'plague inc 2.0',
|
||||
'False',
|
||||
'True',
|
||||
'chèvre'
|
||||
] # Добавьте сюда слова, которые нужно игнорировать
|
||||
|
||||
# Список файлов, которые нужно игнорировать
|
||||
ignore_files = ['italian.ftl', 'popup.ftl', 'controls.ftl', 'input.ftl', 'speech-chatsan.ftl', 'speech-liar.ftl', 'russian.ftl'] # Добавьте сюда имена файлов, которые нужно игнорировать
|
||||
|
||||
# Получаем директорию, где находится скрипт
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Устанавливаем корневую директорию и файл отчета
|
||||
localization_directory = os.path.join(script_dir, '../../Resources/Locale/ru-RU')
|
||||
prototypes_directory = os.path.join(script_dir, '../../Resources/Prototypes')
|
||||
report_path = os.path.join(script_dir, 'Report.txt')
|
||||
|
||||
# Создаём/очищаем файл отчёта
|
||||
open(report_path, 'w').close()
|
||||
|
||||
# Проверяем локализацию
|
||||
localization_issues_found = check_translations(localization_directory, report_path, ignore_list, ignore_files)
|
||||
|
||||
# Проверяем прототипы на наличие русских символов
|
||||
#prototypes_issues_found = check_prototypes_for_russian(prototypes_directory, report_path)
|
||||
|
||||
print(f"Отчет создан в {report_path}")
|
||||
91
Tools/_sunrise/file.py
Normal file
91
Tools/_sunrise/file.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import typing
|
||||
|
||||
from fluent.syntax import ast
|
||||
from yamlmodels import YAMLElements
|
||||
import os
|
||||
|
||||
|
||||
class File:
|
||||
def __init__(self, full_path):
|
||||
self.full_path = full_path
|
||||
|
||||
def read_data(self):
|
||||
file = open(self.full_path, 'r', encoding='utf8')
|
||||
# replace необходим для того, чтобы 1-е сообщение не считалось ast.Junk
|
||||
file_data = file.read().replace('', '')
|
||||
file.close()
|
||||
|
||||
return file_data
|
||||
|
||||
def save_data(self, file_data: typing.AnyStr):
|
||||
os.makedirs(os.path.dirname(self.full_path), exist_ok=True)
|
||||
file = open(self.full_path, 'w', encoding='utf8')
|
||||
file.write(file_data)
|
||||
file.close()
|
||||
|
||||
def get_relative_path(self, base_path):
|
||||
return os.path.relpath(self.full_path, base_path)
|
||||
|
||||
def get_relative_path_without_extension(self, base_path):
|
||||
return self.get_relative_path(base_path).split('.', maxsplit=1)[0]
|
||||
|
||||
def get_relative_parent_dir(self, base_path):
|
||||
return os.path.relpath(self.get_parent_dir(), base_path)
|
||||
|
||||
def get_parent_dir(self):
|
||||
return os.path.dirname(self.full_path)
|
||||
|
||||
def get_name(self):
|
||||
return os.path.basename(self.full_path).split('.')[0]
|
||||
|
||||
|
||||
class FluentFile(File):
|
||||
def __init__(self, full_path):
|
||||
super().__init__(full_path)
|
||||
self.full_path = full_path
|
||||
|
||||
def parse_data(self, file_data: typing.AnyStr):
|
||||
from fluent.syntax import FluentParser
|
||||
|
||||
return FluentParser().parse(file_data)
|
||||
|
||||
def serialize_data(self, parsed_file_data: ast.Resource):
|
||||
from fluent.syntax import FluentSerializer
|
||||
|
||||
return FluentSerializer(with_junk=True).serialize(parsed_file_data)
|
||||
|
||||
def read_serialized_data(self):
|
||||
return self.serialize_data(self.parse_data(self.read_data()))
|
||||
|
||||
def read_parsed_data(self):
|
||||
return self.parse_data(self.read_data())
|
||||
|
||||
|
||||
class YAMLFluentFileAdapter(File):
|
||||
def __init__(self, full_path):
|
||||
super().__init__(full_path)
|
||||
self.full_path = full_path
|
||||
|
||||
# def create_fluent_from_yaml_elements(self, yaml_elements):
|
||||
|
||||
|
||||
|
||||
class YAMLFile(File):
|
||||
def __init__(self, full_path):
|
||||
super().__init__(full_path)
|
||||
|
||||
def parse_data(self, file_data: typing.AnyStr):
|
||||
import yaml
|
||||
|
||||
return yaml.load(file_data, Loader=yaml.BaseLoader)
|
||||
|
||||
def get_elements(self, parsed_data):
|
||||
|
||||
if isinstance(parsed_data, list):
|
||||
elements = YAMLElements(parsed_data).elements
|
||||
# элемент может быть None, если имеет неизвестный тип
|
||||
exist_elements = list(filter(lambda el: el, elements))
|
||||
|
||||
return exist_elements
|
||||
|
||||
return []
|
||||
187
Tools/_sunrise/fluentast.py
Normal file
187
Tools/_sunrise/fluentast.py
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import typing
|
||||
|
||||
from fluent.syntax import ast, FluentParser, FluentSerializer
|
||||
from lokalisemodels import LokaliseKey
|
||||
from pydash import py_
|
||||
|
||||
|
||||
class FluentAstAbstract:
|
||||
element = None
|
||||
@classmethod
|
||||
def get_id_name(cls, element):
|
||||
if isinstance(element, ast.Junk):
|
||||
return FluentAstJunk(element).get_id_name()
|
||||
elif isinstance(element, ast.Message):
|
||||
return FluentAstMessage(element).get_id_name()
|
||||
elif isinstance(element, ast.Term):
|
||||
return FluentAstTerm(element).get_id_name()
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def create_element(cls, element):
|
||||
if isinstance(element, ast.Junk):
|
||||
cls.element = FluentAstJunk(element)
|
||||
return cls.element
|
||||
elif isinstance(element, ast.Message):
|
||||
cls.element = FluentAstMessage(element)
|
||||
return cls.element
|
||||
elif isinstance(element, ast.Term):
|
||||
cls.element = FluentAstTerm(element)
|
||||
return cls.element
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class FluentAstMessage:
|
||||
def __init__(self, message: ast.Message):
|
||||
self.message = message
|
||||
self.element = message
|
||||
|
||||
def get_id_name(self):
|
||||
return self.message.id.name
|
||||
|
||||
|
||||
class FluentAstTerm:
|
||||
def __init__(self, term: ast.Term):
|
||||
self.term = term
|
||||
self.element = term
|
||||
|
||||
def get_id_name(self):
|
||||
return self.term.id.name
|
||||
|
||||
|
||||
class FluentAstAttribute:
|
||||
def __init__(self, id, value, parent_key = None):
|
||||
self.id = id
|
||||
self.value = value
|
||||
self.parent_key = parent_key
|
||||
|
||||
|
||||
class FluentAstAttributeFactory:
|
||||
@classmethod
|
||||
def from_yaml_element(cls, element):
|
||||
attrs = []
|
||||
if element.description:
|
||||
attrs.append(FluentAstAttribute('desc', element.description))
|
||||
|
||||
if element.suffix:
|
||||
attrs.append(FluentAstAttribute('suffix', element.suffix))
|
||||
|
||||
if not len(attrs):
|
||||
return None
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
class FluentAstJunk:
|
||||
def __init__(self, junk: ast.Junk):
|
||||
self.junk = junk
|
||||
self.element = junk
|
||||
|
||||
def get_id_name(self):
|
||||
return self.junk.content.split('=')[0].strip()
|
||||
|
||||
|
||||
class FluentSerializedMessage:
|
||||
@classmethod
|
||||
def from_yaml_element(cls, id, value, attributes, parent_id = None, raw_key = False):
|
||||
if not value and not id and not parent_id:
|
||||
return None
|
||||
|
||||
if not attributes:
|
||||
attributes = []
|
||||
|
||||
if len(list(filter(lambda attr: attr.id == 'desc', attributes))) == 0:
|
||||
if parent_id:
|
||||
attributes.append(FluentAstAttribute('desc', '{ ' + FluentSerializedMessage.get_key(parent_id) + '.desc' + ' }'));
|
||||
else:
|
||||
attributes.append(FluentAstAttribute('desc', '{ "" }'))
|
||||
|
||||
message = f'{cls.get_key(id, raw_key)} = {cls.get_value(value, parent_id)}\n'
|
||||
|
||||
if attributes and len(attributes):
|
||||
full_message = message
|
||||
|
||||
for attr in attributes:
|
||||
fluent_newlines = attr.value.replace("\n", "\n ");
|
||||
full_message = cls.add_attr(full_message, attr.id, fluent_newlines, raw_key=raw_key)
|
||||
|
||||
desc_attr = py_.find(attributes, lambda a: a.id == 'desc')
|
||||
if not desc_attr and parent_id:
|
||||
full_message = cls.add_attr(full_message, 'desc', '{ ' + FluentSerializedMessage.get_key(parent_id) + '.desc' + ' }')
|
||||
|
||||
return full_message
|
||||
|
||||
return cls.to_serialized_message(message)
|
||||
|
||||
@classmethod
|
||||
def from_lokalise_keys(cls, keys: typing.List[LokaliseKey]):
|
||||
attributes_keys = list(filter(lambda k: k.is_attr, keys))
|
||||
attributes = list(map(lambda k: FluentAstAttribute(id='.{name}'.format(name=k.get_key_last_name(k.key_name)),
|
||||
value=FluentSerializedMessage.get_attr(k, k.get_key_last_name(k.key_name)), parent_key=k.get_parent_key()),
|
||||
attributes_keys))
|
||||
attributes_group = py_.group_by(attributes, 'parent_key')
|
||||
|
||||
serialized_message = ''
|
||||
for key in keys:
|
||||
if key.is_attr:
|
||||
continue
|
||||
key_name = key.get_key_last_name(key.key_name)
|
||||
key_value = key.get_translation('ru').data['translation']
|
||||
key_attributes = []
|
||||
|
||||
if len(attributes_group):
|
||||
k = f'{key.get_key_base_name(key.key_name)}.{key_name}'
|
||||
key_attributes = attributes_group[k] if k in attributes_group else []
|
||||
|
||||
message = key.serialize_message()
|
||||
full_message = cls.from_yaml_element(key_name, key_value, key_attributes, key.get_parent_key(), True)
|
||||
|
||||
if full_message:
|
||||
serialized_message = serialized_message + '\n' + full_message
|
||||
elif message:
|
||||
serialized_message = serialized_message + '\n' + message
|
||||
else:
|
||||
raise Exception('Что-то пошло не так')
|
||||
|
||||
return serialized_message
|
||||
|
||||
@staticmethod
|
||||
def get_attr(k, name, parent_id = None):
|
||||
if parent_id:
|
||||
return "{ " + parent_id + f'.{name}' + " }"
|
||||
else:
|
||||
return k.get_translation('ru').data['translation']
|
||||
|
||||
|
||||
@staticmethod
|
||||
def to_serialized_message(string_message):
|
||||
if not string_message:
|
||||
return None
|
||||
|
||||
ast_message = FluentParser().parse(string_message)
|
||||
serialized = FluentSerializer(with_junk=True).serialize(ast_message)
|
||||
|
||||
return serialized if serialized else ''
|
||||
|
||||
@staticmethod
|
||||
def add_attr(message_str, attr_key, attr_value, raw_key = False):
|
||||
prefix = '' if raw_key else '.'
|
||||
return f'{message_str}\n {prefix}{attr_key} = {attr_value}'
|
||||
|
||||
@staticmethod
|
||||
def get_value(value, parent_id):
|
||||
if value:
|
||||
return value
|
||||
elif parent_id:
|
||||
return '{ ' + FluentSerializedMessage.get_key(parent_id) + ' }'
|
||||
else:
|
||||
return '{ "" }'
|
||||
|
||||
@staticmethod
|
||||
def get_key(id, raw = False):
|
||||
if raw:
|
||||
return f'{id}'
|
||||
else:
|
||||
return f'ent-{id}'
|
||||
100
Tools/_sunrise/fluentastcomparer.py
Normal file
100
Tools/_sunrise/fluentastcomparer.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
from fluent.syntax import ast
|
||||
from fluentast import FluentAstAbstract
|
||||
from pydash import py_
|
||||
|
||||
|
||||
class FluentAstComparer:
|
||||
def __init__(self, sourse_parsed: ast.Resource, target_parsed: ast.Resource):
|
||||
self.sourse_parsed = sourse_parsed
|
||||
self.target_parsed = target_parsed
|
||||
self.source_elements = list(
|
||||
filter(lambda el: el, list(map(lambda e: FluentAstAbstract.create_element(e), sourse_parsed.body))))
|
||||
self.target_elements = list(
|
||||
filter(lambda el: el, list(map(lambda e: FluentAstAbstract.create_element(e), target_parsed.body))))
|
||||
|
||||
# Возвращает полностью эквивалентные сообщения (не считая span)
|
||||
def get_equal_elements(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span'])
|
||||
|
||||
return py_.intersection_with(self.source_elements, self.target_elements, comparator=comparator)
|
||||
|
||||
# Возвращает полностью неэквивалентные сообщения (не считая span)
|
||||
def get_not_equal_elements(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span'])
|
||||
diff = py_.difference_with(self.source_elements, self.target_elements, comparator=comparator)
|
||||
|
||||
return diff
|
||||
|
||||
# Возвращает сообщения с эквивалентными именами ключей
|
||||
def get_equal_id_names(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'value', 'comment', 'attributes'])
|
||||
eq = py_.intersection_with(self.source_elements, self.target_elements, comparator=comparator)
|
||||
|
||||
return eq
|
||||
|
||||
# Возвращает сообщения с неэквивалентными именами ключей
|
||||
def get_not_equal_id_names(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'value', 'comment', 'attributes'])
|
||||
diff = py_.difference_with(self.source_elements, self.target_elements, comparator=comparator)
|
||||
|
||||
return diff
|
||||
|
||||
# Возвращает сообщения target, существующие в source
|
||||
def get_exist_id_names(self, source, target):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'value', 'comment', 'attributes'])
|
||||
eq = py_.intersection_with(source, target, comparator=comparator)
|
||||
|
||||
return eq
|
||||
|
||||
# Возвращает сообщения target, существующие в source
|
||||
def get_not_exist_id_names(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'value', 'comment', 'attributes'])
|
||||
diff = py_.difference_with(self.target_elements, self.source_elements, comparator=comparator)
|
||||
|
||||
return diff
|
||||
|
||||
# Возвращает сообщения с эквивалентным значением и атрибутами
|
||||
def get_equal_values_with_attrs(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'id', 'comment'])
|
||||
eq = py_.intersection_with(self.target_elements, self.source_elements, comparator=comparator)
|
||||
|
||||
return eq
|
||||
|
||||
# Возвращает сообщения из source с неэквивалентным значением и атрибутами
|
||||
def get_not_equal_values_with_attrs(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'id', 'comment'])
|
||||
diff = py_.difference_with(self.source_elements, self.target_elements,
|
||||
comparator=lambda a, b: a.element.equals(b.element,
|
||||
ignored_fields=['span', 'id', 'comment']))
|
||||
|
||||
return diff
|
||||
|
||||
# Возвращает сообщения из source, существующие в target и source, с неэквивалентным значением и атрибутами
|
||||
def get_not_equal_exist_values_with_attrs(self):
|
||||
diff = py_.difference_with(self.source_elements, self.target_elements,
|
||||
comparator=lambda a, b: a.element.equals(b.element,
|
||||
ignored_fields=['span', 'id', 'comment']))
|
||||
ex = self.get_exist_id_names(self.source_elements, self.target_elements)
|
||||
exist = py_.intersection(diff, ex)
|
||||
|
||||
return exist
|
||||
|
||||
# Возвращает сообщения из target с неэквивалентным значением и атрибутами
|
||||
|
||||
def get_target_not_equal_values_with_attrs(self):
|
||||
comparator = lambda a, b: a.element.equals(b.element, ignored_fields=['span', 'id', 'comment'])
|
||||
diff = py_.difference_with(self.source_elements, self.target_elements, comparator=comparator)
|
||||
|
||||
return diff
|
||||
|
||||
# Возвращает сообщения, существующие в target и source, с неэквивалентным значением и атрибутами
|
||||
def get_target_not_equal_exist_values_with_attrs(self):
|
||||
diff = py_.difference_with(self.target_elements, self.source_elements,
|
||||
comparator=lambda a, b: a.element.equals(b.element,
|
||||
ignored_fields=['span', 'id', 'comment']))
|
||||
exist = py_.intersection(diff, self.get_exist_id_names(self.target_elements, self.source_elements))
|
||||
|
||||
return exist
|
||||
|
||||
def find_message_by_id_name(self, id_name, list):
|
||||
return py_.find(list, lambda el: el.get_id_name() == id_name)
|
||||
25
Tools/_sunrise/fluentastmanager.py
Normal file
25
Tools/_sunrise/fluentastmanager.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from fluent.syntax import ast
|
||||
from fluentast import FluentAstAbstract
|
||||
|
||||
|
||||
class FluentAstManager:
|
||||
def __init__(self, sourse_parsed: ast.Resource, target_parsed: ast.Resource):
|
||||
self.sourse_parsed = sourse_parsed
|
||||
self.target_parsed = target_parsed
|
||||
self.source_elements = list(map(lambda e: FluentAstAbstract.create_element(e), sourse_parsed.body))
|
||||
self.target_elements = list(map(lambda e: FluentAstAbstract.create_element(e), target_parsed.body))
|
||||
|
||||
def update_by_index(self, index, update_element: ast.Message):
|
||||
source_element = None
|
||||
|
||||
try:
|
||||
source_element = self.sourse_parsed.body[index]
|
||||
except:
|
||||
raise Exception(f'Нет элемента с индексом {index}')
|
||||
|
||||
if not source_element:
|
||||
raise Exception(f'Элемен с индексом {index} не существует')
|
||||
|
||||
self.sourse_parsed.body[index] = update_element
|
||||
|
||||
return self.sourse_parsed
|
||||
37
Tools/_sunrise/fluentformatter.py
Normal file
37
Tools/_sunrise/fluentformatter.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Форматтер, приводящий fluent-файлы (.ftl) в соответствие стайлгайду
|
||||
# path - путь к папке, содержащий форматируемые файлы. Для форматирования всего проекта, необходимо заменить значение на root_dir_path
|
||||
import typing
|
||||
|
||||
from file import FluentFile
|
||||
from project import Project
|
||||
from fluent.syntax import ast, FluentParser, FluentSerializer
|
||||
|
||||
|
||||
######################################### Class defifitions ############################################################
|
||||
|
||||
class FluentFormatter:
|
||||
@classmethod
|
||||
def format(cls, fluent_files: typing.List[FluentFile]):
|
||||
for file in fluent_files:
|
||||
file_data = file.read_data()
|
||||
parsed_file_data = file.parse_data(file_data)
|
||||
serialized_file_data = file.serialize_data(parsed_file_data)
|
||||
file.save_data(serialized_file_data)
|
||||
|
||||
@classmethod
|
||||
def format_serialized_file_data(cls, file_data: typing.AnyStr):
|
||||
parsed_data = FluentParser().parse(file_data)
|
||||
|
||||
return FluentSerializer(with_junk=True).serialize(parsed_data)
|
||||
|
||||
|
||||
|
||||
######################################## Var definitions ###############################################################
|
||||
project = Project()
|
||||
fluent_files = project.get_fluent_files_by_dir(project.ru_locale_dir_path)
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
FluentFormatter.format(fluent_files)
|
||||
2
Tools/_sunrise/key.env
Normal file
2
Tools/_sunrise/key.env
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
lokalise_project_id=4706525766ab4ce5734b82.40263496
|
||||
lokalise_personal_token=cccdf4f3add308b8af995039f5c4846494a6df49
|
||||
220
Tools/_sunrise/keyfinder.py
Normal file
220
Tools/_sunrise/keyfinder.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import typing
|
||||
import logging
|
||||
|
||||
from pydash import py_
|
||||
|
||||
from file import FluentFile
|
||||
from fluentast import FluentAstAbstract
|
||||
from fluentformatter import FluentFormatter
|
||||
from project import Project
|
||||
from fluent.syntax import ast, FluentParser, FluentSerializer
|
||||
|
||||
|
||||
# Осуществляет актуализацию ключей. Находит файлы английского перевода, проверяет: есть ли русскоязычная пара
|
||||
# Если нет - создаёт файл с копией переводов из англоязычного
|
||||
# Далее, пофайлово проверяются ключи. Если в английском файле больше ключей - создает недостающие в русском, с английской копией перевода
|
||||
# Отмечает русские файлы, в которых есть те ключи, что нет в аналогичных английских
|
||||
# Отмечает русские файлы, у которых нет англоязычной пары
|
||||
|
||||
######################################### Class defifitions ############################################################
|
||||
class RelativeFile:
|
||||
def __init__(self, file: FluentFile, locale: typing.AnyStr, relative_path_from_locale: typing.AnyStr):
|
||||
self.file = file
|
||||
self.locale = locale
|
||||
self.relative_path_from_locale = relative_path_from_locale
|
||||
|
||||
|
||||
class FilesFinder:
|
||||
def __init__(self, project: Project):
|
||||
self.project: Project = project
|
||||
self.created_files: typing.List[FluentFile] = []
|
||||
|
||||
def get_relative_path_dict(self, file: FluentFile, locale):
|
||||
if locale == 'ru-RU':
|
||||
return RelativeFile(file=file, locale=locale,
|
||||
relative_path_from_locale=file.get_relative_path(self.project.ru_locale_dir_path))
|
||||
elif locale == 'en-US':
|
||||
return RelativeFile(file=file, locale=locale,
|
||||
relative_path_from_locale=file.get_relative_path(self.project.en_locale_dir_path))
|
||||
else:
|
||||
raise Exception(f'Локаль {locale} не поддерживается')
|
||||
|
||||
def get_file_pair(self, en_file: FluentFile) -> typing.Tuple[FluentFile, FluentFile]:
|
||||
ru_file_path = en_file.full_path.replace('en-US', 'ru-RU')
|
||||
ru_file = FluentFile(ru_file_path)
|
||||
|
||||
return en_file, ru_file
|
||||
|
||||
def execute(self):
|
||||
self.created_files = []
|
||||
groups = self.get_files_pars()
|
||||
keys_without_pair = list(filter(lambda g: len(groups[g]) < 2, groups))
|
||||
|
||||
for key_without_pair in keys_without_pair:
|
||||
relative_file: RelativeFile = groups.get(key_without_pair)[0]
|
||||
|
||||
if relative_file.locale == 'en-US':
|
||||
ru_file = self.create_ru_analog(relative_file)
|
||||
self.created_files.append(ru_file)
|
||||
elif relative_file.locale == 'ru-RU':
|
||||
is_engine_files = "robust-toolbox" in (relative_file.file.full_path)
|
||||
is_corvax_files = "corvax" in (relative_file.file.full_path)
|
||||
if not is_engine_files and not is_corvax_files:
|
||||
self.warn_en_analog_not_exist(relative_file)
|
||||
else:
|
||||
raise Exception(f'Файл {relative_file.file.full_path} имеет неизвестную локаль "{relative_file.locale}"')
|
||||
|
||||
return self.created_files
|
||||
|
||||
def get_files_pars(self):
|
||||
en_fluent_files = self.project.get_fluent_files_by_dir(project.en_locale_dir_path)
|
||||
ru_fluent_files = self.project.get_fluent_files_by_dir(project.ru_locale_dir_path)
|
||||
|
||||
en_fluent_relative_files = list(map(lambda f: self.get_relative_path_dict(f, 'en-US'), en_fluent_files))
|
||||
ru_fluent_relative_files = list(map(lambda f: self.get_relative_path_dict(f, 'ru-RU'), ru_fluent_files))
|
||||
relative_files = py_.flatten_depth(py_.concat(en_fluent_relative_files, ru_fluent_relative_files), depth=1)
|
||||
|
||||
return py_.group_by(relative_files, 'relative_path_from_locale')
|
||||
|
||||
def create_ru_analog(self, en_relative_file: RelativeFile) -> FluentFile:
|
||||
en_file: FluentFile = en_relative_file.file
|
||||
en_file_data = en_file.read_data()
|
||||
ru_file_path = en_file.full_path.replace('en-US', 'ru-RU')
|
||||
ru_file = FluentFile(ru_file_path)
|
||||
ru_file.save_data(en_file_data)
|
||||
|
||||
logging.info(f'Создан файл {ru_file_path} с переводами из английского файла')
|
||||
|
||||
return ru_file
|
||||
|
||||
def warn_en_analog_not_exist(self, ru_relative_file: RelativeFile):
|
||||
file: FluentFile = ru_relative_file.file
|
||||
en_file_path = file.full_path.replace('ru-RU', 'en-US')
|
||||
|
||||
logging.warning(f'Файл {file.full_path} не имеет английского аналога по пути {en_file_path}')
|
||||
|
||||
|
||||
class KeyFinder:
|
||||
def __init__(self, files_dict):
|
||||
self.files_dict = files_dict
|
||||
self.changed_files: typing.List[FluentFile] = []
|
||||
|
||||
def execute(self) -> typing.List[FluentFile]:
|
||||
self.changed_files = []
|
||||
for pair in self.files_dict:
|
||||
ru_relative_file = py_.find(self.files_dict[pair], {'locale': 'ru-RU'})
|
||||
en_relative_file = py_.find(self.files_dict[pair], {'locale': 'en-US'})
|
||||
|
||||
if not en_relative_file or not ru_relative_file:
|
||||
continue
|
||||
|
||||
ru_file: FluentFile = ru_relative_file.file
|
||||
en_file: FluentFile = en_relative_file.file
|
||||
|
||||
self.compare_files(en_file, ru_file)
|
||||
|
||||
return self.changed_files
|
||||
|
||||
|
||||
def compare_files(self, en_file, ru_file):
|
||||
ru_file_parsed: ast.Resource = ru_file.parse_data(ru_file.read_data())
|
||||
en_file_parsed: ast.Resource = en_file.parse_data(en_file.read_data())
|
||||
|
||||
self.write_to_ru_files(ru_file, ru_file_parsed, en_file_parsed)
|
||||
self.log_not_exist_en_files(en_file, ru_file_parsed, en_file_parsed)
|
||||
|
||||
|
||||
def write_to_ru_files(self, ru_file, ru_file_parsed, en_file_parsed):
|
||||
for idx, en_message in enumerate(en_file_parsed.body):
|
||||
if isinstance(en_message, ast.ResourceComment) or isinstance(en_message, ast.GroupComment) or isinstance(en_message, ast.Comment):
|
||||
continue
|
||||
|
||||
ru_message_analog_idx = py_.find_index(ru_file_parsed.body, lambda ru_message: self.find_duplicate_message_id_name(ru_message, en_message))
|
||||
have_changes = False
|
||||
|
||||
# Attributes
|
||||
if getattr(en_message, 'attributes', None) and ru_message_analog_idx != -1:
|
||||
if not ru_file_parsed.body[ru_message_analog_idx].attributes:
|
||||
ru_file_parsed.body[ru_message_analog_idx].attributes = en_message.attributes
|
||||
have_changes = True
|
||||
else:
|
||||
for en_attr in en_message.attributes:
|
||||
ru_attr_analog = py_.find(ru_file_parsed.body[ru_message_analog_idx].attributes, lambda ru_attr: ru_attr.id.name == en_attr.id.name)
|
||||
if not ru_attr_analog:
|
||||
ru_file_parsed.body[ru_message_analog_idx].attributes.append(en_attr)
|
||||
have_changes = True
|
||||
|
||||
# New elements
|
||||
if ru_message_analog_idx == -1:
|
||||
ru_file_body = ru_file_parsed.body
|
||||
if (len(ru_file_body) >= idx + 1):
|
||||
ru_file_parsed = self.append_message(ru_file_parsed, en_message, idx)
|
||||
else:
|
||||
ru_file_parsed = self.push_message(ru_file_parsed, en_message)
|
||||
have_changes = True
|
||||
|
||||
if have_changes:
|
||||
serialized = serializer.serialize(ru_file_parsed)
|
||||
self.save_and_log_file(ru_file, serialized, en_message)
|
||||
|
||||
def log_not_exist_en_files(self, en_file, ru_file_parsed, en_file_parsed):
|
||||
for idx, ru_message in enumerate(ru_file_parsed.body):
|
||||
if isinstance(ru_message, ast.ResourceComment) or isinstance(ru_message, ast.GroupComment) or isinstance(ru_message, ast.Comment):
|
||||
continue
|
||||
|
||||
en_message_analog = py_.find(en_file_parsed.body, lambda en_message: self.find_duplicate_message_id_name(ru_message, en_message))
|
||||
|
||||
if not en_message_analog:
|
||||
logging.warning(f'Ключ "{FluentAstAbstract.get_id_name(ru_message)}" не имеет английского аналога по пути {en_file.full_path}"')
|
||||
|
||||
def append_message(self, ru_file_parsed, en_message, en_message_idx):
|
||||
ru_message_part_1 = ru_file_parsed.body[0:en_message_idx]
|
||||
ru_message_part_middle = [en_message]
|
||||
ru_message_part_2 = ru_file_parsed.body[en_message_idx:]
|
||||
new_body = py_.flatten_depth([ru_message_part_1, ru_message_part_middle, ru_message_part_2], depth=1)
|
||||
ru_file_parsed.body = new_body
|
||||
|
||||
return ru_file_parsed
|
||||
|
||||
def push_message(self, ru_file_parsed, en_message):
|
||||
ru_file_parsed.body.append(en_message)
|
||||
return ru_file_parsed
|
||||
|
||||
def save_and_log_file(self, file, file_data, message):
|
||||
file.save_data(file_data)
|
||||
logging.info(f'В файл {file.full_path} добавлен ключ "{FluentAstAbstract.get_id_name(message)}"')
|
||||
self.changed_files.append(file)
|
||||
|
||||
def find_duplicate_message_id_name(self, ru_message, en_message):
|
||||
ru_element_id_name = FluentAstAbstract.get_id_name(ru_message)
|
||||
en_element_id_name = FluentAstAbstract.get_id_name(en_message)
|
||||
|
||||
if not ru_element_id_name or not en_element_id_name:
|
||||
return False
|
||||
|
||||
if ru_element_id_name == en_element_id_name:
|
||||
return ru_message
|
||||
else:
|
||||
return None
|
||||
|
||||
######################################## Var definitions ###############################################################
|
||||
|
||||
logging.basicConfig(level = logging.INFO)
|
||||
project = Project()
|
||||
parser = FluentParser()
|
||||
serializer = FluentSerializer(with_junk=True)
|
||||
files_finder = FilesFinder(project)
|
||||
key_finder = KeyFinder(files_finder.get_files_pars())
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
print('Проверка актуальности файлов ...')
|
||||
created_files = files_finder.execute()
|
||||
if len(created_files):
|
||||
print('Форматирование созданных файлов ...')
|
||||
FluentFormatter.format(created_files)
|
||||
print('Проверка актуальности ключей ...')
|
||||
changed_files = key_finder.execute()
|
||||
if len(changed_files):
|
||||
print('Форматирование изменённых файлов ...')
|
||||
FluentFormatter.format(changed_files)
|
||||
48
Tools/_sunrise/lokalise_fluent_ast_comparer_manager.py
Normal file
48
Tools/_sunrise/lokalise_fluent_ast_comparer_manager.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
from fluent.syntax import ast
|
||||
|
||||
from fluentast import FluentAstMessage
|
||||
from fluentastcomparer import FluentAstComparer
|
||||
from fluentastmanager import FluentAstManager
|
||||
|
||||
|
||||
class LokaliseFluentAstComparerManager:
|
||||
def __init__(self, sourse_parsed: ast.Resource, target_parsed: ast.Resource):
|
||||
self.sourse_parsed = sourse_parsed
|
||||
self.target_parsed = target_parsed
|
||||
self.comparer = FluentAstComparer(sourse_parsed, target_parsed)
|
||||
self.ast_manager = FluentAstManager(sourse_parsed, target_parsed)
|
||||
|
||||
def for_update(self):
|
||||
for_update = self.comparer.get_not_equal_exist_values_with_attrs()
|
||||
|
||||
if not len(for_update):
|
||||
return []
|
||||
|
||||
return for_update
|
||||
|
||||
def update(self, for_update):
|
||||
for update in for_update:
|
||||
idx = self.comparer.sourse_parsed.body.index(update.element)
|
||||
update_mess: FluentAstMessage = self.comparer.find_message_by_id_name(update.get_id_name(),
|
||||
self.comparer.target_elements)
|
||||
self.ast_manager.update_by_index(idx, update_mess.element)
|
||||
|
||||
return self.ast_manager.sourse_parsed
|
||||
|
||||
def for_delete(self):
|
||||
for_delete = self.comparer.get_not_exist_id_names()
|
||||
|
||||
if len(for_delete):
|
||||
keys = list(map(lambda el: el.get_id_name(), for_delete))
|
||||
print(f'Следующие ключи есть в lokalise, но нет в файле. Возможно, их нужно удалить из lokalise: {keys}')
|
||||
|
||||
return for_delete
|
||||
|
||||
def for_create(self):
|
||||
for_create = self.comparer.get_not_equal_id_names()
|
||||
|
||||
if len(for_create):
|
||||
keys = list(map(lambda el: el.get_id_name(), for_create))
|
||||
print(f'Следующих ключей файла нет в lokalise. Необходимо добавить: {keys}')
|
||||
|
||||
return for_create
|
||||
33
Tools/_sunrise/lokalise_project.py
Normal file
33
Tools/_sunrise/lokalise_project.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import lokalise
|
||||
import typing
|
||||
from lokalisemodels import LokaliseKey
|
||||
from pydash import py_
|
||||
|
||||
class LokaliseProject:
|
||||
def __init__(self, project_id, personal_token):
|
||||
self.project_id = "4706525766ab4ce5734b82.40263496"
|
||||
self.personal_token = "cccdf4f3add308b8af995039f5c4846494a6df49"
|
||||
self.client = lokalise.Client(self.personal_token)
|
||||
|
||||
def get_all_keys(self) -> typing.List[LokaliseKey]:
|
||||
page = 1
|
||||
keys = self.get_keys(page=page)
|
||||
keys_items: typing.List[lokalise.client.KeyModel] = []
|
||||
general_count = 0
|
||||
|
||||
while (general_count < keys.total_count):
|
||||
general_count = general_count + len(keys.items)
|
||||
keys_items = py_.flatten_depth(py_.concat(keys_items, keys.items), depth=1)
|
||||
|
||||
if (general_count == keys.total_count):
|
||||
break
|
||||
|
||||
next_page = page = page + 1
|
||||
keys = self.get_keys(page=next_page)
|
||||
|
||||
sorted_list = py_.sort(keys_items, key=lambda item: item.translations_modified_at_timestamp, reverse=True)
|
||||
|
||||
return list(map(lambda k: LokaliseKey(k), sorted_list))
|
||||
|
||||
def get_keys(self, page):
|
||||
return self.client.keys(self.project_id, {'page': page, 'limit': 5000, 'include_translations': 1})
|
||||
64
Tools/_sunrise/lokalisemodels.py
Normal file
64
Tools/_sunrise/lokalisemodels.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import typing
|
||||
import os
|
||||
from pydash import py_
|
||||
from project import Project
|
||||
|
||||
class LocalePath:
|
||||
def __init__(self, relative_file_path):
|
||||
self.ru = os.path.join(Project().ru_locale_dir_path, relative_file_path)
|
||||
self.en = os.path.join(Project().en_locale_dir_path, relative_file_path)
|
||||
|
||||
|
||||
class LokaliseTranslation:
|
||||
def __init__(self, data, key_name: typing.AnyStr):
|
||||
self.key_name = key_name,
|
||||
self.data = data
|
||||
|
||||
class LokaliseKey:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.key_name = self.data.key_name['web']
|
||||
self.key_base_name = self.get_key_base_name(self.key_name)
|
||||
self.is_attr = self.check_is_attr()
|
||||
|
||||
def get_file_path(self):
|
||||
|
||||
relative_dir_path = '{relative_file_path}.ftl'.format(
|
||||
relative_file_path='/'.join(self.data.key_name['web'].split('.')[0].split('::')))
|
||||
|
||||
return LocalePath(relative_dir_path)
|
||||
|
||||
def get_key_base_name(self, key_name):
|
||||
splitted_name = key_name.split('.')
|
||||
return splitted_name[0]
|
||||
|
||||
def get_key_last_name(self, key_name):
|
||||
splitted_name = key_name.split('.')
|
||||
return py_.last(splitted_name)
|
||||
|
||||
def get_parent_key(self):
|
||||
if self.is_attr:
|
||||
splitted_name = self.key_name.split('.')[0:-1]
|
||||
return '.'.join(splitted_name)
|
||||
else:
|
||||
return None
|
||||
|
||||
def check_is_attr(self):
|
||||
return len(self.key_name.split('.')) > 2
|
||||
|
||||
def serialize(self):
|
||||
if self.is_attr:
|
||||
return self.serialize_attr()
|
||||
else:
|
||||
return self.serialize_message()
|
||||
|
||||
|
||||
|
||||
def serialize_attr(self):
|
||||
return '.{name} = {value}'.format(name=self.get_key_last_name(self.key_name), value=self.get_translation('ru').data['translation'])
|
||||
|
||||
def serialize_message(self):
|
||||
return '{name} = {value}'.format(name=self.get_key_last_name(self.key_name), value=self.get_translation('ru').data['translation'])
|
||||
|
||||
def get_translation(self, language_iso='ru'):
|
||||
return list(map(lambda data: LokaliseTranslation(key_name=self.data.key_name['web'], data=data), py_.filter(self.data.translations, {'language_iso': language_iso})))[0]
|
||||
31
Tools/_sunrise/project.py
Normal file
31
Tools/_sunrise/project.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import pathlib
|
||||
import os
|
||||
import glob
|
||||
from file import FluentFile
|
||||
|
||||
class Project:
|
||||
def __init__(self):
|
||||
self.base_dir_path = pathlib.Path(os.path.abspath(os.curdir)).parent.parent.resolve()
|
||||
self.resources_dir_path = os.path.join(self.base_dir_path, 'Resources')
|
||||
self.locales_dir_path = os.path.join(self.resources_dir_path, 'Locale')
|
||||
self.ru_locale_dir_path = os.path.join(self.locales_dir_path, 'ru-RU')
|
||||
self.en_locale_dir_path = os.path.join(self.locales_dir_path, 'en-US')
|
||||
self.prototypes_dir_path = os.path.join(self.resources_dir_path, "Prototypes")
|
||||
self.en_locale_prototypes_dir_path = os.path.join(self.en_locale_dir_path, 'ss14-ru', 'prototypes')
|
||||
self.ru_locale_prototypes_dir_path = os.path.join(self.ru_locale_dir_path, 'ss14-ru', 'prototypes')
|
||||
|
||||
def get_files_paths_by_dir(self, dir_path, files_extenstion):
|
||||
return glob.glob(f'{dir_path}/**/*.{files_extenstion}', recursive=True)
|
||||
|
||||
def get_fluent_files_by_dir(self, dir_path):
|
||||
files = []
|
||||
files_paths_list = glob.glob(f'{dir_path}/**/*.ftl', recursive=True)
|
||||
|
||||
for file_path in files_paths_list:
|
||||
try:
|
||||
files.append(FluentFile(file_path))
|
||||
except:
|
||||
continue
|
||||
|
||||
return files
|
||||
|
||||
BIN
Tools/_sunrise/requirements.txt
Normal file
BIN
Tools/_sunrise/requirements.txt
Normal file
Binary file not shown.
67
Tools/_sunrise/translationassembler.py
Normal file
67
Tools/_sunrise/translationassembler.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import logging
|
||||
import typing
|
||||
|
||||
from fluent.syntax import FluentParser, FluentSerializer
|
||||
from pydash import py_
|
||||
|
||||
from file import FluentFile
|
||||
from fluentast import FluentSerializedMessage
|
||||
from lokalise_fluent_ast_comparer_manager import LokaliseFluentAstComparerManager
|
||||
from lokalise_project import LokaliseProject
|
||||
from lokalisemodels import LokaliseKey
|
||||
import os
|
||||
|
||||
######################################### Class defifitions ############################################################
|
||||
|
||||
# TODO непереведенные элементы приходят как { "" }. Необходимо сохранять английский перевод
|
||||
class TranslationsAssembler:
|
||||
def __init__(self, items: typing.List[LokaliseKey]):
|
||||
self.group = py_.group_by(items, 'key_base_name')
|
||||
keys = list(self.group.keys())
|
||||
self.sorted_keys = py_.sort_by(keys, lambda key: self.sort_by_translations_timestamp(self.group[key]),
|
||||
reverse=True)
|
||||
|
||||
def execute(self):
|
||||
for keys in self.group:
|
||||
full_message = FluentSerializedMessage.from_lokalise_keys(self.group[keys])
|
||||
parsed_message = FluentParser().parse(full_message)
|
||||
ru_full_path = self.group[keys][0].get_file_path().ru
|
||||
ru_file = FluentFile(ru_full_path)
|
||||
try:
|
||||
ru_file_parsed = ru_file.read_parsed_data()
|
||||
except:
|
||||
logging.error(f'Файла {ru_file.full_path} не существует')
|
||||
continue
|
||||
|
||||
manager = LokaliseFluentAstComparerManager(sourse_parsed=ru_file_parsed, target_parsed=parsed_message)
|
||||
|
||||
for_update = manager.for_update()
|
||||
for_create = manager.for_create()
|
||||
for_delete = manager.for_delete()
|
||||
|
||||
if len(for_update):
|
||||
updated_ru_file_parsed = manager.update(for_update)
|
||||
updated_ru_file_serialized = FluentSerializer(with_junk=True).serialize(updated_ru_file_parsed)
|
||||
ru_file.save_data(updated_ru_file_serialized)
|
||||
|
||||
updated_keys = list(map(lambda el: el.get_id_name(), for_update))
|
||||
logging.info(f'Обновлены ключи: {updated_keys} в файле {ru_file.full_path}')
|
||||
|
||||
def sort_by_translations_timestamp(self, list):
|
||||
sorted_list = py_.sort_by(list, 'data.translations_modified_at_timestamp', reverse=True)
|
||||
|
||||
return sorted_list[0].data.translations_modified_at_timestamp
|
||||
|
||||
######################################## Var definitions ###############################################################
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
lokalise_project_id = os.getenv('lokalise_project_id')
|
||||
lokalise_personal_token = os.getenv('lokalise_personal_token')
|
||||
lokalise_project = LokaliseProject(project_id=lokalise_project_id,
|
||||
personal_token=lokalise_personal_token)
|
||||
all_keys: typing.List[LokaliseKey] = lokalise_project.get_all_keys()
|
||||
translations_assembler = TranslationsAssembler(all_keys)
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
translations_assembler.execute()
|
||||
107
Tools/_sunrise/yamlextractor.py
Normal file
107
Tools/_sunrise/yamlextractor.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import os
|
||||
|
||||
from fluent.syntax.parser import FluentParser
|
||||
from fluent.syntax.serializer import FluentSerializer
|
||||
|
||||
from file import YAMLFile, FluentFile
|
||||
from fluentast import FluentSerializedMessage, FluentAstAttributeFactory
|
||||
from fluentformatter import FluentFormatter
|
||||
from project import Project
|
||||
import logging
|
||||
|
||||
######################################### Class defifitions ############################################################
|
||||
class YAMLExtractor:
|
||||
def __init__(self, yaml_files):
|
||||
self.yaml_files = yaml_files
|
||||
|
||||
def execute(self):
|
||||
for yaml_file in yaml_files:
|
||||
yaml_elements = yaml_file.get_elements(yaml_file.parse_data(yaml_file.read_data()))
|
||||
|
||||
if not len(yaml_elements):
|
||||
continue
|
||||
|
||||
fluent_file_serialized = self.get_serialized_fluent_from_yaml_elements(yaml_elements)
|
||||
|
||||
if not fluent_file_serialized:
|
||||
continue
|
||||
|
||||
pretty_fluent_file_serialized = formatter.format_serialized_file_data(fluent_file_serialized)
|
||||
|
||||
relative_parent_dir = yaml_file.get_relative_parent_dir(project.prototypes_dir_path).lower()
|
||||
file_name = yaml_file.get_name()
|
||||
|
||||
en_fluent_file_path = self.create_en_fluent_file(relative_parent_dir, file_name, pretty_fluent_file_serialized)
|
||||
ru_fluent_file_path = self.create_ru_fluent_file(en_fluent_file_path)
|
||||
|
||||
def get_serialized_fluent_from_yaml_elements(self, yaml_elements):
|
||||
fluent_serialized_messages = list(
|
||||
map(lambda el: FluentSerializedMessage.from_yaml_element(el.id, el.name, FluentAstAttributeFactory.from_yaml_element(el), el.parent_id), yaml_elements)
|
||||
)
|
||||
fluent_exist_serialized_messages = list(filter(lambda m: m, fluent_serialized_messages))
|
||||
|
||||
if not len(fluent_exist_serialized_messages):
|
||||
return None
|
||||
|
||||
return '\n'.join(fluent_exist_serialized_messages)
|
||||
|
||||
def create_en_fluent_file(self, relative_parent_dir, file_name, file_data):
|
||||
# Проверяем, если информация берется из директории _Sunrise
|
||||
if '_sunrise' in relative_parent_dir:
|
||||
# Создаем путь для сохранения в en-US/_sunrise/_prototypes
|
||||
en_new_dir_path = os.path.join(project.en_locale_dir_path, '_sunrise', '_prototypes', relative_parent_dir.replace('_sunrise', '').strip(os.sep))
|
||||
else:
|
||||
# Обычный путь сохранения
|
||||
en_new_dir_path = os.path.join(project.en_locale_prototypes_dir_path, relative_parent_dir)
|
||||
|
||||
# Убедимся, что директория существует
|
||||
os.makedirs(en_new_dir_path, exist_ok=True)
|
||||
|
||||
# Формируем полный путь к файлу
|
||||
en_fluent_file_path = os.path.join(en_new_dir_path, f'{file_name}.ftl')
|
||||
|
||||
# Сохраняем файл
|
||||
en_fluent_file = FluentFile(en_fluent_file_path)
|
||||
en_fluent_file.save_data(file_data)
|
||||
|
||||
logging.info(f'Актуализирован файл английской локализации {en_fluent_file.full_path}')
|
||||
|
||||
return en_fluent_file.full_path
|
||||
|
||||
def create_ru_fluent_file(self, en_analog_file_path):
|
||||
if '_sunrise' in en_analog_file_path.lower():
|
||||
# Если уже есть _sunrise, просто заменяем en-US на ru-RU
|
||||
ru_file_full_path = en_analog_file_path.replace('en-US', 'ru-RU')
|
||||
else:
|
||||
# Обычный путь сохранения
|
||||
ru_file_full_path = en_analog_file_path.replace('en-US', 'ru-RU')
|
||||
|
||||
# Убедимся, что директория существует
|
||||
os.makedirs(os.path.dirname(ru_file_full_path), exist_ok=True)
|
||||
|
||||
if os.path.isfile(ru_file_full_path):
|
||||
return
|
||||
else:
|
||||
en_file = FluentFile(f'{en_analog_file_path}')
|
||||
file = FluentFile(f'{ru_file_full_path}')
|
||||
file.save_data(en_file.read_data())
|
||||
logging.info(f'Создан файл русской локализации {ru_file_full_path}')
|
||||
|
||||
return ru_file_full_path
|
||||
|
||||
|
||||
######################################## Var definitions ###############################################################
|
||||
|
||||
logging.basicConfig(level = logging.INFO)
|
||||
project = Project()
|
||||
serializer = FluentSerializer()
|
||||
parser = FluentParser()
|
||||
formatter = FluentFormatter()
|
||||
|
||||
yaml_files_paths = project.get_files_paths_by_dir(project.prototypes_dir_path, 'yml')
|
||||
yaml_files = list(map(lambda yaml_file_path: YAMLFile(yaml_file_path), yaml_files_paths))
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
logging.info(f'Поиск yaml-файлов ...')
|
||||
YAMLExtractor(yaml_files).execute()
|
||||
26
Tools/_sunrise/yamlmodels.py
Normal file
26
Tools/_sunrise/yamlmodels.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
class YAMLEntity:
|
||||
def __init__(self, id, name, description, suffix, parent_id = None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.suffix = suffix
|
||||
self.parent_id = parent_id
|
||||
|
||||
|
||||
class YAMLElements:
|
||||
def __init__(self, items):
|
||||
self.elements = list(map(lambda i: self.create_element(i), items))
|
||||
|
||||
def create_element(self, item):
|
||||
if not 'id' in item:
|
||||
return None
|
||||
|
||||
if item['type'] == 'entity':
|
||||
entity = YAMLEntity(item['id'], item['name'] if 'name' in item else None,
|
||||
item['description'] if 'description' in item else None,
|
||||
item['suffix'] if 'suffix' in item else None,
|
||||
item['parent'] if 'parent' in item else None
|
||||
)
|
||||
return entity
|
||||
else:
|
||||
return None
|
||||
Loading…
Add table
Reference in a new issue