{"id":488549,"date":"2026-07-25T18:07:01","date_gmt":"2026-07-25T18:07:01","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=488549"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=488549","title":{"rendered":"\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 App Store \u0438 Google Play \u0441 fastlane \u0438 Azure Pipelines"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/db1\/bc0\/533\/db1bc05332f4a7f1d998a59729a1159e.jpg\" width=\"1536\" height=\"1024\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/db1\/bc0\/533\/db1bc05332f4a7f1d998a59729a1159e.jpg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/db1\/bc0\/533\/db1bc05332f4a7f1d998a59729a1159e.jpg 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 20+\u00a0\u044f\u0437\u044b\u043a\u043e\u0432.\u00a0\u041a\u0430\u0436\u0434\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u00a0\u2014\u00a0\u044d\u0442\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u0430\u044f \u0441\u0431\u043e\u0440\u043a\u0430,\u00a0\u043d\u043e \u0438 \u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u043e\u0431\u043e\u0438\u0445 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430\u0445:\u00a0\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435,\u00a0\u043f\u043e\u0434\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a,\u00a0\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435,\u00a0\u00ab\u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb.\u00a0\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430,\u00a0\u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435,\u00a0\u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.<\/p>\n<p>\u041f\u0440\u0438 \u0434\u0432\u0443\u0445 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u0445 \u044f\u0437\u044b\u043a\u043e\u0432 \u0438 \u0434\u0432\u0443\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430\u0445 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u00a0\u00ab\u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb\u00a0\u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 30\u201340 \u043c\u0438\u043d\u0443\u0442.\u00a0\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043b\u0435\u0433\u043a\u043e \u043e\u0448\u0438\u0431\u0438\u0442\u044c\u0441\u044f:\u00a0\u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u0443 \u0432\u0435\u0440\u0441\u0438\u044e \u0442\u0435\u043a\u0441\u0442\u0430,\u00a0\u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d \u044f\u0437\u044b\u043a,\u00a0\u043f\u0435\u0440\u0435\u043f\u0443\u0442\u0430\u0442\u044c \u043f\u043e\u043b\u044f \u043c\u0435\u0436\u0434\u0443 iOS \u0438 Android.<\/p>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u043f\u043e\u043a\u0430\u0436\u0443,\u00a0\u043a\u0430\u043a \u0441\u0432\u0435\u0441\u0442\u0438 \u0432\u0441\u0451 \u044d\u0442\u043e \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u043a\u043e\u043c\u043c\u0438\u0442\u0443.<\/p>\n<hr\/>\n<h3>Fastlane \u0432 \u0434\u0432\u0443\u0445 \u0441\u043b\u043e\u0432\u0430\u0445<\/h3>\n<p><a href=\"https:\/\/fastlane.tools\/\" rel=\"noopener noreferrer nofollow\">fastlane<\/a>\u00a0\u2014\u00a0Ruby-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e CI\/CD.\u00a0\u0414\u0432\u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442:<\/p>\n<ul>\n<li>\n<p><strong>deliver<\/strong>\u00a0\u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 App Store Connect<\/p>\n<\/li>\n<li>\n<p><strong>supply<\/strong>\u00a0\u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 Google Play<\/p>\n<\/li>\n<\/ul>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f:\u00a0\u043e\u0431\u0430 \u0447\u0438\u0442\u0430\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435\u00a0<strong>\u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430 \u0434\u0438\u0441\u043a\u0435<\/strong>,\u00a0\u0430 \u043d\u0435 \u0438\u0437 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430.\u00a0\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435\u00a0<code>.txt<\/code>-\u0444\u0430\u0439\u043b\u044b \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438\u00a0\u2014\u00a0\u0438 fastlane \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435.<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c fastlane \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e.\u00a0\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u00a0<code>Gemfile<\/code>\u00a0\u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438\u00a0\u2014\u00a0CI \u0441\u043a\u0430\u0447\u0430\u0435\u0442 \u0432\u0441\u0451 \u043d\u0443\u0436\u043d\u043e\u0435 \u0441\u0430\u043c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435:<\/p>\n<pre><code class=\"ruby\"># Gemfilesource \"https:\/\/rubygems.org\"gem \"fastlane\", \"~&gt; 2.235\"# \u042f\u0432\u043d\u043e \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u043d\u0437\u0438\u0442\u0438\u0432\u043d\u0443\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0441\u0431\u043e\u0435\u0432 \u043d\u0430 CI-\u0430\u0433\u0435\u043d\u0442\u0430\u0445gem \"multi_json\", \"&gt;= 1.14.1\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b \u043d\u0430 \u044f\u0437\u044b\u043a<\/h3>\n<p>Fastlane \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0434\u0435\u0440\u0435\u0432\u0430\u00a0<code>.txt<\/code>-\u0444\u0430\u0439\u043b\u043e\u0432:<\/p>\n<pre><code>fastlane\/metadata\/  ios\/    en-US\/      name.txt      subtitle.txt      keywords.txt      promotional_text.txt      description.txt      release_notes.txt    de-DE\/      ...  android\/    en-US\/      title.txt      short_description.txt      full_description.txt      changelogs\/        default.txt    de-DE\/      ...<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e:\u00a0\u043c\u043d\u043e\u0433\u043e \u043c\u0435\u043b\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432,\u00a0\u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439 diff \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445,\u00a0\u043b\u0435\u0433\u043a\u043e \u0441\u0431\u0438\u0442\u044c\u0441\u044f \u0441 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u043e\u043c \u043a\u043e\u0434\u043e\u0432 \u043b\u043e\u043a\u0430\u043b\u0435\u0439.<\/p>\n<p><strong>\u0420\u0435\u0448\u0435\u043d\u0438\u0435:<\/strong>\u00a0\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0445 Markdown-\u0444\u0430\u0439\u043b\u0430\u0445\u00a0\u2014\u00a0\u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u043d\u0430 \u044f\u0437\u044b\u043a.\u00a0Python-\u0441\u043a\u0440\u0438\u043f\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0438\u0437 \u043d\u0438\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u043b\u044f fastlane \u043f\u0440\u044f\u043c\u043e \u0432 CI \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.\u00a0\u041f\u0430\u043f\u043a\u0430\u00a0<code>fastlane\/metadata\/<\/code>\u00a0\u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f\u00a0\u2014\u00a0\u044d\u0442\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442 \u0441\u0431\u043e\u0440\u043a\u0438,\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0438 \u0438\u0441\u0447\u0435\u0437\u0430\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0430\u0433\u0435\u043d\u0442\u043e\u043c.<\/p>\n<pre><code>stores\/               \u2190 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0442\u0435\u043a\u0441\u0442\u044b, \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 git  STORES.md           \u2190 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 (\u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439, en-US)  STORES-de.md  STORES-fr-FR.md  STORES-ja.md  STORES-ru.md  ...scripts\/  prepare_metadata.py \u2190 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 fastlane\/metadata\/ \u0438\u0437 stores\/Gemfile               \u2190 fastlane \u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f CI<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>\u0424\u043e\u0440\u043c\u0430\u0442 STORES-\u0444\u0430\u0439\u043b\u0430<\/h3>\n<p>\u042d\u0442\u043e \u043d\u0435 \u043a\u0430\u043a\u043e\u0439 \u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u0430 \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0434 \u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u0444\u043e\u0440\u043c\u0430\u0442. \u041a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0435\u043a\u0446\u0438\u0438 \u0441 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c\u0438.\u00a0\u041a\u043e\u043d\u0442\u0435\u043d\u0442\u00a0\u2014\u00a0\u0432 \u0431\u043b\u043e\u043a\u0430\u0445\u00a0<code>```text<\/code>\u00a0(\u0447\u0442\u043e\u0431\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u043d\u0435 \u043c\u0435\u0448\u0430\u043b\u0438 \u0438 \u043b\u0438\u043c\u0438\u0442\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0431\u044b\u043b\u0438 \u0432\u0438\u0434\u043d\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435).\u00a0\u0412 \u043a\u043e\u043d\u0446\u0435 \u0444\u0430\u0439\u043b\u0430\u00a0\u2014\u00a0\u0441\u0435\u043a\u0446\u0438\u044f\u00a0\u00ab\u0427\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb\u00a0\u0441 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0439,\u00a0\u043d\u043e\u0432\u0435\u0439\u0448\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u0435\u0440\u0432\u0430\u044f:<\/p>\n<pre><code class=\"markdown\">&lt;!-- Language: German --&gt;### Title```textMeineApp - Kurzer Slogan```### Appstore keywords (100 characters max)```textproduktivit\u00e4t,aufgaben,planung,timer,fokus,notizen,erinnerung,kalender```### Appstore subtitle (30 characters max)```textAufgaben einfach erledigen```### Appstore promotional text (170 characters max)```textJetzt mit neuen Widgets f\u00fcr den Startbildschirm!Behalte deine Aufgaben immer im Blick.```### Google Play short description (80 characters max)```textEinfache Aufgabenverwaltung mit Timer und Erinnerungen.```### Full description (Google Play and AppStore)```textMeineApp hilft dir, den \u00dcberblick \u00fcber deine Aufgaben zu behalten.FUNKTIONEN- Aufgaben mit F\u00e4lligkeitsdatum und Priorit\u00e4t- Integrierter Fokus-Timer- Erinnerungen und Benachrichtigungen- Widgets f\u00fcr den Startbildschirm- Synchronisierung zwischen Ger\u00e4ten```### What's New#### 2.4Neue Widgets f\u00fcr den Startbildschirm. Performance-Verbesserungen.#### 2.3Fehlerbehebungen und Stabilit\u00e4tsverbesserungen.<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u043e\u0434\u043d\u0438\u043c \u0444\u0430\u0439\u043b\u043e\u043c \u043d\u0430 \u044f\u0437\u044b\u043a.\u00a0<code>git diff<\/code>\u00a0\u043d\u0430 PR \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u043e,\u00a0\u0447\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c.\u00a0\u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u043b\u0438\u0437\u043e\u043c\u00a0\u2014\u00a0\u0434\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0448\u044c \u043d\u043e\u0432\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c\u00a0<code>#### 2.5<\/code>\u00a0\u0432\u0432\u0435\u0440\u0445 \u0432\u00a0<code>What's New<\/code>.<\/p>\n<hr\/>\n<h3>\u0421\u043a\u0440\u0438\u043f\u0442: \u0438\u0437 Markdown \u0432 fastlane<\/h3>\n<p>\u0421\u043a\u0440\u0438\u043f\u0442\u00a0<code>scripts\/prepare_metadata.py<\/code>\u00a0\u0447\u0438\u0442\u0430\u0435\u0442 \u0432\u0441\u0435\u00a0<code>STORES-*.md<\/code>,\u00a0\u043f\u0430\u0440\u0441\u0438\u0442 \u0441\u0435\u043a\u0446\u0438\u0438 \u0438 \u043f\u0438\u0448\u0435\u0442\u00a0<code>.txt<\/code>-\u0444\u0430\u0439\u043b\u044b \u0432 \u043d\u0443\u0436\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443.\u00a0\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 CI \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u044b.<\/p>\n<pre><code class=\"python\">#!\/usr\/bin\/env python3\"\"\"prepare_metadata.py \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 fastlane\/metadata\/ \u0438\u0437 stores\/STORES-*.md.\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:    python scripts\/prepare_metadata.py [--dry-run]\u0421\u043e\u0437\u0434\u0430\u0451\u0442:    fastlane\/metadata\/ios\/{locale}\/        name.txt, subtitle.txt, keywords.txt,        promotional_text.txt, description.txt, release_notes.txt    fastlane\/metadata\/android\/{locale}\/        title.txt, short_description.txt, full_description.txt,        changelogs\/default.txt\"\"\"import reimport sysfrom pathlib import PathROOT       = Path(__file__).resolve().parent.parentSTORES_DIR = ROOT \/ \"stores\"IOS_META   = ROOT \/ \"fastlane\" \/ \"metadata\" \/ \"ios\"AND_META   = ROOT \/ \"fastlane\" \/ \"metadata\" \/ \"android\"DRY_RUN = \"--dry-run\" in sys.argv# \u041c\u0430\u043f\u043f\u0438\u043d\u0433: \u043a\u043b\u044e\u0447 \u0444\u0430\u0439\u043b\u0430 \u2192 (ios_locale | None, android_locale | None)# None \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a \u2014 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c.# \u041a\u043b\u044e\u0447 \"\" \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 STORES.md (\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, en-US).LOCALE_MAP: dict[str, tuple[str | None, str | None]] = {    \"\":        (\"en-US\",   \"en-US\"),    \"ar\":      (\"ar-SA\",   \"ar\"),    \"bg\":      (None,      \"bg\"),        # App Store \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 bg    \"cs\":      (\"cs\",      \"cs-CZ\"),    \"da\":      (\"da\",      \"da-DK\"),    \"de\":      (\"de-DE\",   \"de-DE\"),    \"el\":      (\"el\",      \"el-GR\"),    \"en-AU\":   (\"en-AU\",   \"en-AU\"),    \"en-CA\":   (\"en-CA\",   \"en-CA\"),    \"en-GB\":   (\"en-GB\",   \"en-GB\"),    \"es-ES\":   (\"es-ES\",   \"es-ES\"),    \"es-MX\":   (\"es-MX\",   \"es-419\"),   # Google Play: es-419 \u0434\u043b\u044f \u041b\u0430\u0442\u0438\u043d\u0441\u043a\u043e\u0439 \u0410\u043c\u0435\u0440\u0438\u043a\u0438    \"fi\":      (\"fi\",      \"fi-FI\"),    \"fr-CA\":   (\"fr-CA\",   \"fr-CA\"),    \"fr-FR\":   (\"fr-FR\",   \"fr-FR\"),    \"he\":      (\"he\",      \"iw-IL\"),    # Google Play \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u043a\u043e\u0434 iw    \"hi\":      (\"hi\",      \"hi-IN\"),    \"hr\":      (\"hr\",      \"hr\"),    \"hu\":      (\"hu\",      \"hu-HU\"),    \"id\":      (\"id\",      \"id\"),    \"it\":      (\"it\",      \"it-IT\"),    \"ja\":      (\"ja\",      \"ja-JP\"),    \"ko\":      (\"ko\",      \"ko-KR\"),    \"nl\":      (\"nl-NL\",   \"nl-NL\"),    \"no\":      (\"no\",      \"no-NO\"),    # App Store: \"no\", Google Play: \"no-NO\"    \"pl\":      (\"pl\",      \"pl-PL\"),    \"pt-BR\":   (\"pt-BR\",   \"pt-BR\"),    \"pt-PT\":   (\"pt-PT\",   \"pt-PT\"),    \"ro\":      (\"ro\",      \"ro\"),    \"ru\":      (\"ru\",      \"ru-RU\"),    \"sv\":      (\"sv\",      \"sv-SE\"),    \"th\":      (\"th\",      \"th\"),    \"tr\":      (\"tr\",      \"tr-TR\"),    \"uk\":      (\"uk\",      \"uk\"),    \"vi\":      (\"vi\",      \"vi\"),    \"zh\":      (\"zh-Hans\", \"zh-CN\"),    \"zh-Hant\": (\"zh-Hant\", \"zh-TW\"),}# \u041b\u043e\u043a\u0430\u043b\u0438 Google Play \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e STORES-\u0444\u0430\u0439\u043b\u0430.# \u0411\u0435\u0440\u0451\u043c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0444\u0430\u0439\u043b\u0430.ANDROID_FALLBACKS: dict[str, str] = {    \"en-IN\": \"\",         # English (India) \u2192 en-US    \"en-SG\": \"\",         # English (Singapore) \u2192 en-US    \"en-ZA\": \"\",         # English (South Africa) \u2192 en-US    \"es-US\": \"es-MX\",   # Spanish (United States) \u2192 es-MX    \"zh-HK\": \"zh-Hant\", # Chinese (Hong Kong) \u2192 zh-Hant}def extract_section(text: str, heading: str) -&gt; str:    \"\"\"\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0431\u043b\u043e\u043a\u0430 ```text ... ``` \u043f\u043e\u0434 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c heading.\"\"\"    pattern = rf\"### {re.escape(heading)}[^\\n]*\\n```text\\n(.*?)```\"    m = re.search(pattern, text, re.DOTALL)    return m.group(1).strip() if m else \"\"def extract_whatsnew(text: str) -&gt; str:    \"\"\"\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043a\u0441\u0442 \u043f\u0435\u0440\u0432\u043e\u0439 (\u043d\u043e\u0432\u0435\u0439\u0448\u0435\u0439) \u0437\u0430\u043f\u0438\u0441\u0438 \u0438\u0437 \u0441\u0435\u043a\u0446\u0438\u0438 ### What's New.\"\"\"    section_m = re.search(r\"### What's New\\n(.*?)(?=\\n### |\\Z)\", text, re.DOTALL)    if not section_m:        return \"\"    section = section_m.group(1)    # \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u043c \u043f\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c \u0432\u0435\u0440\u0441\u0438\u0439 \u0438 \u0431\u0435\u0440\u0451\u043c \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u043b\u043e\u043a    parts = re.split(r\"^#### .+$\", section, flags=re.MULTILINE)    return parts[1].strip() if len(parts) &gt;= 2 else \"\"def parse_stores_file(path: Path) -&gt; dict[str, str]:    content = path.read_text(encoding=\"utf-8\")    return {        \"title\":       extract_section(content, \"Title\"),        \"keywords\":    extract_section(content, \"Appstore keywords\"),        \"subtitle\":    extract_section(content, \"Appstore subtitle\"),        \"promo\":       extract_section(content, \"Appstore promotional text\"),        \"short_desc\":  extract_section(content, \"Google Play short description\"),        \"description\": extract_section(content, \"Full description\"),        \"whatsnew\":    extract_whatsnew(content),    }def write_file(path: Path, content: str) -&gt; None:    if DRY_RUN:        print(f\"  [dry] {path}\")        return    path.parent.mkdir(parents=True, exist_ok=True)    path.write_text(content + \"\\n\", encoding=\"utf-8\")def write_ios(locale: str, data: dict[str, str]) -&gt; None:    base = IOS_META \/ locale    fields = {        \"name.txt\":             data[\"title\"],        \"subtitle.txt\":         data[\"subtitle\"],        \"keywords.txt\":         data[\"keywords\"],        \"promotional_text.txt\": data[\"promo\"],        \"description.txt\":      data[\"description\"],        \"release_notes.txt\":    data[\"whatsnew\"],    }    for filename, content in fields.items():        if content:            write_file(base \/ filename, content)def write_android(locale: str, data: dict[str, str]) -&gt; None:    base = AND_META \/ locale    fields = {        \"title.txt\":             data[\"title\"],        \"short_description.txt\": data[\"short_desc\"],        \"full_description.txt\":  data[\"description\"],    }    for filename, content in fields.items():        if content:            write_file(base \/ filename, content)    if data[\"whatsnew\"]:        write_file(base \/ \"changelogs\" \/ \"default.txt\", data[\"whatsnew\"])def stores_key(path: Path) -&gt; str:    \"\"\"STORES-fr-FR.md \u2192 'fr-FR', STORES.md \u2192 ''\"\"\"    stem = path.stem    return \"\" if stem == \"STORES\" else stem[len(\"STORES-\"):]def main() -&gt; None:    store_files  = sorted(STORES_DIR.glob(\"STORES*.md\"))    parsed_cache: dict[str, dict[str, str]] = {}    unmapped: list[str] = []    # \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0430\u0440\u0441\u0438\u043c \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b, \u0447\u0442\u043e\u0431\u044b en-US \u043c\u043e\u0433 \u0441\u0442\u0430\u0442\u044c \u0444\u043e\u043b\u0431\u044d\u043a\u043e\u043c \u0434\u043b\u044f What's New    for path in store_files:        key = stores_key(path)        if key not in LOCALE_MAP:            unmapped.append(key)            continue        parsed_cache[key] = parse_stores_file(path)    # \u0410\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 changelog \u0438\u0437 STORES.md \u2014 \u0444\u043e\u043b\u0431\u044d\u043a, \u043a\u043e\u0433\u0434\u0430 \u0443 \u043b\u043e\u043a\u0430\u043b\u0438 \u043d\u0435\u0442 \u0441\u0432\u043e\u0435\u0433\u043e    # What's New. \u041c\u043e\u0436\u043d\u043e \u0432\u0435\u0441\u0442\u0438 changelog \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c, \u0438 \u0432\u0441\u0435 \u044f\u0437\u044b\u043a\u0438 \u0432\u043e\u0437\u044c\u043c\u0443\u0442    # \u0435\u0433\u043e; \u043f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u043f\u043e\u0437\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f per-locale \u0441\u0435\u043a\u0446\u0438\u0438 What's New.    default_whatsnew = parsed_cache.get(\"\", {}).get(\"whatsnew\", \"\")    for key, data in parsed_cache.items():        ios_locale, android_locale = LOCALE_MAP[key]        if not data[\"whatsnew\"]:            data[\"whatsnew\"] = default_whatsnew        label = key or \"en-US (default)\"        parts = []        if ios_locale:            write_ios(ios_locale, data)            parts.append(f\"iOS:{ios_locale}\")        if android_locale:            write_android(android_locale, data)            parts.append(f\"Android:{android_locale}\")        print(f\"  {label:12s} \u2192 {', '.join(parts)}\")    # \u0424\u043e\u043b\u0431\u044d\u043a\u0438 \u0434\u043b\u044f Google Play (\u043b\u043e\u043a\u0430\u043b\u0438 \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e STORES-\u0444\u0430\u0439\u043b\u0430)    for android_locale, source_key in ANDROID_FALLBACKS.items():        source_path = STORES_DIR \/ (f\"STORES-{source_key}.md\" if source_key else \"STORES.md\")        data = parsed_cache.get(source_key) or parse_stores_file(source_path)        if not data[\"whatsnew\"]:            data[\"whatsnew\"] = default_whatsnew        write_android(android_locale, data)        print(f\"  {android_locale:12s} \u2192 Android:{android_locale} (via {source_key or 'en-US'})\")    if unmapped:        print(f\"\\nWarning: \u043d\u0435\u0442 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430 \u0434\u043b\u044f: {', '.join(unmapped)}\")        print(\"  \u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 LOCALE_MAP.\")    if not DRY_RUN:        print(f\"\\niOS     \u2192 {IOS_META}\")        print(f\"Android \u2192 {AND_META}\")if __name__ == \"__main__\":    main()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435:<\/p>\n<pre><code>  en-US (default) \u2192 iOS:en-US, Android:en-US  de           \u2192 iOS:de-DE, Android:de-DE  fr-FR        \u2192 iOS:fr-FR, Android:fr-FR  he           \u2192 iOS:he, Android:iw-IL  es-MX        \u2192 iOS:es-MX, Android:es-419  ...  en-IN        \u2192 Android:en-IN (via en-US)  zh-HK        \u2192 Android:zh-HK (via zh-Hant)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0424\u043b\u0430\u0433\u00a0<code>--dry-run<\/code>\u00a0\u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0443\u0442\u0438 \u0444\u0430\u0439\u043b\u043e\u0432,\u00a0\u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0438\u0448\u0435\u0442 \u043d\u0430 \u0434\u0438\u0441\u043a\u00a0\u2014\u00a0\u0443\u0434\u043e\u0431\u043d\u043e \u043f\u0440\u043e\u0433\u043d\u0430\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0434 \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u043c,\u00a0\u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f,\u00a0\u0447\u0442\u043e \u0432\u0441\u0435 \u0441\u0435\u043a\u0446\u0438\u0438 \u0440\u0430\u0441\u043f\u0430\u0440\u0441\u0438\u043b\u0438\u0441\u044c \u0438 \u043b\u043e\u043a\u0430\u043b\u0438 \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u043b\u0438\u0441\u044c \u043f\u043e \u043d\u0443\u0436\u043d\u044b\u043c \u043f\u0430\u043f\u043a\u0430\u043c.<\/p>\n<hr\/>\n<h3>\u0423\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435<\/h3>\n<p>Fastlane \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0444\u0430\u0439\u043b\u044b \u0441 \u043a\u043b\u044e\u0447\u0430\u043c\u0438,\u00a0\u0430 \u043d\u0435 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b\u00a0\u2014\u00a0\u044d\u0442\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u043c\u0438 \u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0432 Azure DevOps.\u00a0\u041e\u0431\u0430 \u043a\u043b\u044e\u0447\u0430 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432\u00a0<strong>Secure Files<\/strong>\u00a0(Pipelines\u00a0\u2192\u00a0Library\u00a0\u2192\u00a0Secure Files)\u00a0\u0438 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 CI \u0437\u0430\u0434\u0430\u0447\u0435\u0439\u00a0<code>DownloadSecureFile@1<\/code>.<\/p>\n<h4>App Store Connect \u2014 API Key<\/h4>\n<p>\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438:\u00a0\u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 Apple ID \u0438 \u043f\u0430\u0440\u043e\u043b\u044f,\u00a0\u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u0434\u0432\u0443\u0445\u0444\u0430\u043a\u0442\u043e\u0440\u043a\u0438.<\/p>\n<p><strong>\u0413\u0434\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c:<\/strong><\/p>\n<p>App Store Connect\u00a0\u2192\u00a0<strong>Users and Access<\/strong>\u00a0\u2192\u00a0<strong>Integrations<\/strong>\u00a0\u2192\u00a0<strong>App Store Connect API<\/strong>\u00a0\u2192\u00a0\u043a\u043d\u043e\u043f\u043a\u0430\u00a0<code>+<\/code><\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b11\/a6b\/048\/b11a6b04887fac7909b4515efde0ee19.png\" width=\"2834\" height=\"1132\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/b11\/a6b\/048\/b11a6b04887fac7909b4515efde0ee19.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b11\/a6b\/048\/b11a6b04887fac7909b4515efde0ee19.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443:<\/p>\n<ul>\n<li>\n<p><strong>Name<\/strong>\u00a0\u2014 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u043e\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440\u00a0<code>CI Fastlane<\/code><\/p>\n<\/li>\n<li>\n<p><strong>Access<\/strong>\u00a0\u2014 \u0440\u043e\u043b\u044c\u00a0<strong>App Manager<\/strong><\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/65e\/a7d\/a57\/65ea7da5797d9a785c3101ac78e9ef09.png\" width=\"1814\" height=\"974\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/65e\/a7d\/a57\/65ea7da5797d9a785c3101ac78e9ef09.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/65e\/a7d\/a57\/65ea7da5797d9a785c3101ac78e9ef09.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u0430\u0436\u043c\u0438\u0442\u0435\u00a0<strong>Download API Key<\/strong>\u00a0\u2014\u00a0\u0441\u043a\u0430\u0447\u0430\u0435\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u00a0<code>AuthKey_XXXXXXXXXX.p8<\/code>.<\/p>\n<blockquote>\n<p><strong>\u0412\u0430\u0436\u043d\u043e:<\/strong>\u00a0\u0444\u0430\u0439\u043b \u043c\u043e\u0436\u043d\u043e \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437.\u00a0\u0415\u0441\u043b\u0438 \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u0438\u00a0\u2014\u00a0\u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447.<\/p>\n<\/blockquote>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d6b\/1ec\/08d\/d6b1ec08d0371921c02033a196affc77.png\" width=\"2320\" height=\"416\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d6b\/1ec\/08d\/d6b1ec08d0371921c02033a196affc77.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d6b\/1ec\/08d\/d6b1ec08d0371921c02033a196affc77.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p><strong>\u041a\u0430\u043a \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c fastlane:<\/strong><\/p>\n<p>Fastlane \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043a\u043b\u044e\u0447 \u0432 \u0432\u0438\u0434\u0435 JSON-\u0444\u0430\u0439\u043b\u0430 \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0442\u0440\u0435\u043c\u044f \u043f\u043e\u043b\u044f\u043c\u0438 \u0441\u0440\u0430\u0437\u0443.\u00a0\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b\u00a0<code>api_key.json<\/code>\u00a0\u0432\u0440\u0443\u0447\u043d\u0443\u044e:<\/p>\n<pre><code class=\"json\">{  \"key_id\": \"XXXXXXXXXX\",  \"issuer_id\": \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\",  \"key\": \"-----BEGIN PRIVATE KEY-----\\nMIGT...\\n-----END PRIVATE KEY-----\"}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<ul>\n<li>\n<p><code>key_id<\/code>\u00a0\u2014 Key ID \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b App Store Connect API<\/p>\n<\/li>\n<li>\n<p><code>issuer_id<\/code>\u00a0\u2014 Issuer ID \u0432\u0432\u0435\u0440\u0445\u0443 \u0442\u043e\u0439 \u0436\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b<\/p>\n<\/li>\n<li>\n<p><code>key<\/code>\u00a0\u2014 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0441\u043a\u0430\u0447\u0430\u043d\u043d\u043e\u0433\u043e\u00a0<code>.p8<\/code>-\u0444\u0430\u0439\u043b\u0430; \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044b \u0441\u0442\u0440\u043e\u043a \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430\u00a0<code>\\n<\/code><\/p>\n<\/li>\n<\/ul>\n<p>\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435\u00a0<code>api_key.json<\/code>\u00a0\u0432 Azure DevOps:\u00a0Pipelines\u00a0\u2192\u00a0Library\u00a0\u2192\u00a0<strong>Secure Files<\/strong>\u00a0\u2192\u00a0Upload file.<\/p>\n<p>\u0412 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0435 \u0444\u0430\u0439\u043b \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u0434\u0430\u0447\u0435\u0439\u00a0<code>DownloadSecureFile@1<\/code>\u00a0\u0438 \u043f\u0443\u0442\u044c \u043a \u043d\u0435\u043c\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>--api_key_path<\/code>:<\/p>\n<pre><code class=\"yaml\">- task: DownloadSecureFile@1  name: appStoreApiKey  inputs:    secureFile: 'api_key.json'- script: |    bundle exec fastlane deliver \\      --api_key_path $(appStoreApiKey.secureFilePath) \\      ...<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h4>Google Play \u2014 Service Account<\/h4>\n<p>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0432 \u0442\u0440\u0438 \u0448\u0430\u0433\u0430:\u00a0\u0441\u0432\u044f\u0437\u0430\u0442\u044c Play Console \u0441 Google Cloud,\u00a0\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0438 \u0432\u044b\u0434\u0430\u0442\u044c \u0435\u043c\u0443 \u043f\u0440\u0430\u0432\u0430.<\/p>\n<h3>\u0428\u0430\u0433 1. \u0421\u0432\u044f\u0437\u0430\u0442\u044c Play Console \u0441 Google Cloud<\/h3>\n<p>Google Play Console\u00a0\u2192\u00a0<strong>Setup<\/strong>\u00a0\u2192\u00a0<strong>API access<\/strong><\/p>\n<p>\u0415\u0441\u043b\u0438 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0435\u0449\u0451 \u043d\u0435 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u00a0\u2014\u00a0\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u0442 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c Google Cloud \u043f\u0440\u043e\u0435\u043a\u0442.<\/p>\n<blockquote>\n<p>\ud83d\udcf8\u00a0<strong>\u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442:<\/strong>\u00a0Google Play Console\u00a0\u2192\u00a0Setup\u00a0\u2192\u00a0API access<br \/>\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c:\u00a0\u0431\u043b\u043e\u043a \u0441 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c Google Cloud \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c \u0438 \u043a\u043d\u043e\u043f\u043a\u0443\u00a0<strong>Create new service account<\/strong><\/p>\n<\/blockquote>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b41\/025\/8d2\/b410258d2a5b07644966fa3fc0ccdb0c.png\" width=\"2120\" height=\"610\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/b41\/025\/8d2\/b410258d2a5b07644966fa3fc0ccdb0c.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b41\/025\/8d2\/b410258d2a5b07644966fa3fc0ccdb0c.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u0428\u0430\u0433 2. \u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0438 \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043a\u043b\u044e\u0447<\/h3>\n<p>\u041d\u0430 \u0442\u043e\u0439 \u0436\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u00a0\u2014\u00a0\u043a\u043d\u043e\u043f\u043a\u0430\u00a0<strong>Create new service account<\/strong>.\u00a0\u041e\u0442\u043a\u0440\u043e\u0435\u0442\u0441\u044f Google Cloud Console.<\/p>\n<p>\u0412 Google Cloud Console:\u00a0<strong>IAM &amp; Admin<\/strong>\u00a0\u2192\u00a0<strong>Service Accounts<\/strong>\u00a0\u2192\u00a0<strong>Create Service Account<\/strong><\/p>\n<ul>\n<li>\n<p><strong>Name:<\/strong>\u00a0<code>fastlane-ci<\/code>\u00a0(\u0438\u043b\u0438 \u043b\u044e\u0431\u043e\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0435)<\/p>\n<\/li>\n<li>\n<p>\u0420\u043e\u043b\u0438 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 \u044d\u0442\u043e\u043c \u0448\u0430\u0433\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u044b<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f:\u00a0\u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u00a0\u2192\u00a0\u0432\u043a\u043b\u0430\u0434\u043a\u0430\u00a0<strong>Keys<\/strong>\u00a0\u2192\u00a0<strong>Add Key<\/strong>\u00a0\u2192\u00a0<strong>Create new key<\/strong>\u00a0\u2192\u00a0\u0442\u0438\u043f\u00a0<strong>JSON<\/strong>.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c50\/351\/59c\/c5035159c13af4d6f190a3af5480b1bf.png\" width=\"1706\" height=\"936\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c50\/351\/59c\/c5035159c13af4d6f190a3af5480b1bf.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c50\/351\/59c\/c5035159c13af4d6f190a3af5480b1bf.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0421\u043a\u0430\u0447\u0430\u0435\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u00a0<code>google_play_key.json<\/code>\u00a0\u2014\u00a0\u044d\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 Google Cloud.<\/p>\n<h3>\u0428\u0430\u0433 3. \u0412\u044b\u0434\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0430 \u0432 Play Console<\/h3>\n<p>\u0412\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0432 Google Play Console\u00a0\u2192\u00a0<strong>Setup<\/strong>\u00a0\u2192\u00a0<strong>API access<\/strong>.\u00a0\u041d\u0430\u0439\u0434\u0438\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442,\u00a0\u043d\u0430\u0436\u043c\u0438\u0442\u0435\u00a0<strong>Grant access<\/strong>.<\/p>\n<ul>\n<li>\n<p><strong>App permissions<\/strong>\u00a0\u2192 \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0441\u0432\u043e\u0451 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p>\u0420\u043e\u043b\u044c:\u00a0<strong>Release manager<\/strong><\/p>\n<\/li>\n<\/ul>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/47e\/3cd\/bb1\/47e3cdbb178cc27b128e57b2c8ca9037.png\" width=\"2590\" height=\"1030\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/47e\/3cd\/bb1\/47e3cdbb178cc27b128e57b2c8ca9037.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/47e\/3cd\/bb1\/47e3cdbb178cc27b128e57b2c8ca9037.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435\u00a0<code>google_play_key.json<\/code>\u00a0\u0432 Azure DevOps Secure Files.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/214\/11b\/315\/21411b3152797b77a1379f5da9d98133.png\" width=\"1786\" height=\"988\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/214\/11b\/315\/21411b3152797b77a1379f5da9d98133.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/214\/11b\/315\/21411b3152797b77a1379f5da9d98133.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0412 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0435 \u043f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437\u00a0<code>--json_key<\/code>:<\/p>\n<pre><code>- task: DownloadSecureFile@1  name: googlePlayKey  inputs:    secureFile: 'google_play_key.json'- script: |    bundle exec fastlane supply \\      --json_key $(googlePlayKey.secureFilePath) \\      ...<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<h3>Azure Pipelines<\/h3>\n<p>\u0421\u043e\u0431\u0435\u0440\u0451\u043c \u0432\u0441\u0451 \u0432\u043c\u0435\u0441\u0442\u0435.\u00a0\u041f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 merge \u0432 \u0432\u0435\u0442\u043a\u0443\u00a0<code>release<\/code>\u00a0\u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043d\u043e\u0432\u044b\u0439 \u0440\u0435\u043b\u0438\u0437 \u0441\u0440\u0430\u0437\u0443 \u0432 \u043e\u0431\u043e\u0438\u0445 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430\u0445:\u00a0\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0441\u0432\u0435\u0436\u0438\u0435 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 \u0438 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0437\u0430 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0445\u043e\u0434.<\/p>\n<p>\u0421\u0430\u043c\u0443 \u0441\u0431\u043e\u0440\u043a\u0443 iOS\/Android \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043e\u043f\u0443\u0441\u043a\u0430\u044e\u00a0\u2014\u00a0\u043e\u043d\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0441\u0442\u0435\u043a\u0430\u00a0(Xcode,\u00a0Gradle)\u00a0\u0438 \u0442\u044f\u043d\u0435\u0442 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u0433\u043e\u0432\u043e\u0440.\u00a0\u0412\u043c\u0435\u0441\u0442\u043e \u043d\u0435\u0451 \u0441\u0442\u043e\u0438\u0442 \u0448\u0430\u0433-\u0437\u0430\u0433\u043b\u0443\u0448\u043a\u0430,\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 IPA \u0438 AAB \u0438 \u0432\u044b\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e\u00a0<code>APP_VERSION<\/code>.\u00a0\u0414\u0430\u043b\u044c\u0448\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d:<\/p>\n<ol>\n<li>\n<p>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438\u0437 Secure Files<\/p>\n<\/li>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 fastlane \u0447\u0435\u0440\u0435\u0437 Bundler (\u0441 \u043a\u0435\u0448\u0435\u043c)<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u00a0<code>prepare_metadata.py<\/code><\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043e\u0431\u0430 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"yaml\"># azure-pipelines.ymltrigger:  branches:    include:      - releasepool:  vmImage: 'macos-15'variables:  - group: AppSecretsstages:  - stage: UpdateStoreMetadata    displayName: 'Update store metadata'    jobs:      - job: UploadMetadata        timeoutInMinutes: 30        steps:          - task: UseRubyVersion@0            displayName: 'Use Ruby 4.0'            inputs:              versionSpec: '4.0'          # \u041a\u0435\u0448 Ruby-\u0433\u0435\u043c\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u043c\u0438 \u2014 \u0443\u0441\u043a\u043e\u0440\u044f\u0435\u0442 CI \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0432\u0434\u0432\u043e\u0435          - task: Cache@2            displayName: 'Cache Ruby gems (Bundler)'            inputs:              key: 'bundler | \"$(Agent.OS)\" | Gemfile'              restoreKeys: |                bundler | \"$(Agent.OS)\"              path: '$(Pipeline.Workspace)\/.bundle-cache'          - script: |              gem install bundler --no-document              export BUNDLE_PATH='$(Pipeline.Workspace)\/.bundle-cache'              bundle install --jobs 4 --retry 3            displayName: 'Install fastlane'          # --- \u0417\u0434\u0435\u0441\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0441\u0431\u043e\u0440\u043a\u0430 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u043e\u0432 ---          # \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0435 \u044d\u0442\u043e\u0442 \u0448\u0430\u0433 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 IPA \u0438 AAB          # \u0438 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 APP_VERSION. \u041a\u043e\u043c\u0430\u043d\u0434\u044b \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u0441\u0442\u0435\u043a\u0430 (Xcode, Gradle)          # \u0438 \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043e\u043f\u0443\u0449\u0435\u043d\u044b.          - script: |              echo \"TODO: \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 IPA \u2192 $(Build.ArtifactStagingDirectory)\/app.ipa\"              echo \"TODO: \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 AAB \u2192 $(Build.ArtifactStagingDirectory)\/app.aab\"              echo \"##vso[task.setvariable variable=APP_VERSION]2.4.0\"            displayName: 'Build binaries (placeholder)'          # \u041a\u043b\u044e\u0447 App Store Connect \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0444\u0430\u0439\u043b          - task: DownloadSecureFile@1            name: appStoreApiKey            displayName: 'Download App Store API Key'            inputs:              secureFile: 'api_key.json'          # \u041a\u043b\u044e\u0447 Google Play \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0444\u0430\u0439\u043b          - task: DownloadSecureFile@1            name: googlePlayKey            displayName: 'Download Google Play Service Account Key'            inputs:              secureFile: 'google_play_key.json'          # \u0413\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c fastlane\/metadata\/ \u0438\u0437 STORES-*.md          - script: |              python3 scripts\/prepare_metadata.py            displayName: 'Prepare store metadata'          # \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0440\u0435\u043b\u0438\u0437 \u0432 App Store Connect: \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a + \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435          - script: |              export BUNDLE_PATH='$(Pipeline.Workspace)\/.bundle-cache'              bundle exec fastlane deliver \\                --api_key_path $(appStoreApiKey.secureFilePath) \\                --app_identifier com.example.myapp \\                --app_version $(APP_VERSION) \\                --ipa $(Build.ArtifactStagingDirectory)\/app.ipa \\                --metadata_path fastlane\/metadata\/ios \\                --skip_screenshots \\                --run_precheck_before_submit false \\                --force            displayName: 'Upload iOS build + metadata to App Store Connect'          # \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0440\u0435\u043b\u0438\u0437 \u0432 Google Play: \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a + \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435          - script: |              export BUNDLE_PATH='$(Pipeline.Workspace)\/.bundle-cache'              bundle exec fastlane supply \\                --json_key $(googlePlayKey.secureFilePath) \\                --package_name com.example.myapp \\                --aab $(Build.ArtifactStagingDirectory)\/app.aab \\                --metadata_path fastlane\/metadata\/android \\                --track internal \\                --skip_upload_screenshots \\                --skip_upload_images            displayName: 'Upload Android build + metadata to Google Play'<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435:<\/p>\n<p><strong>Gemfile \u0432\u043c\u0435\u0441\u0442\u043e \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0439 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438.<\/strong>\u00a0Fastlane \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0430\u0433\u0435\u043d\u0442 \u0437\u0430\u0440\u0430\u043d\u0435\u0435\u00a0\u2014\u00a0\u043e\u043d \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 Bundler \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u0430.\u00a0Gemfile \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0432\u0435\u0440\u0441\u0438\u044e\u00a0(<code>~&gt; 2.235<\/code>),\u00a0\u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 fastlane\u00a0\u2014\u00a0\u044d\u0442\u043e \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0438\u0442 \u0432 \u0440\u0435\u043f\u043e,\u00a0\u0430 \u043d\u0435 \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u0441\u044e\u0440\u043f\u0440\u0438\u0437 \u043e\u0442 \u0430\u0433\u0435\u043d\u0442\u0430.<\/p>\n<p><strong>\u041a\u0435\u0448 Bundler.<\/strong>\u00a0\u0411\u0435\u0437 \u043a\u0435\u0448\u0430\u00a0<code>bundle install<\/code>\u00a0\u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 2\u20133 \u043c\u0438\u043d\u0443\u0442\u044b.\u00a0\u0421 \u043a\u0435\u0448\u0435\u043c\u00a0\u2014\u00a0\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434.\u00a0\u041a\u043b\u044e\u0447 \u043a\u0435\u0448\u0430 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u00a0<code>Gemfile<\/code>,\u00a0\u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438 \u0441\u043c\u0435\u043d\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u043a\u0435\u0448 \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.\u00a0<code>BUNDLE_PATH<\/code>\u00a0\u043d\u0443\u0436\u043d\u043e \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u044f\u0432\u043d\u043e \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u044b\u043c\u00a0<code>bundle exec<\/code>\u00a0\u2014\u00a0\u0437\u0430\u0434\u0430\u0447\u0430\u00a0<code>Cache@2<\/code>\u00a0\u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0444\u0430\u0439\u043b\u044b \u0432 \u043f\u0430\u043f\u043a\u0443,\u00a0\u043d\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u043d\u0435 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442.<\/p>\n<p><strong>Secure Files \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b.<\/strong>\u00a0\u041e\u0431\u0430 \u043a\u043b\u044e\u0447\u0430\u00a0\u2014\u00a0\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0435 JSON-\u0444\u0430\u0439\u043b\u044b.\u00a0\u0425\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043a\u0430\u043a \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 Variable Group \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e:\u00a0\u044d\u043a\u0440\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435,\u00a0\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u044b \u0441\u0442\u0440\u043e\u043a,\u00a0\u0440\u0438\u0441\u043a \u043e\u0448\u0438\u0431\u043a\u0438.\u00a0Secure Files \u0440\u0435\u0448\u0430\u044e\u0442 \u044d\u0442\u043e \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u0435\u0435:\u00a0\u0444\u0430\u0439\u043b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u0447\u0435\u0440\u0435\u0437 UI,\u00a0\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0437\u0430\u0434\u0430\u0447\u0435\u0439\u00a0<code>DownloadSecureFile@1<\/code>,\u00a0\u043f\u0443\u0442\u044c \u043a \u043d\u0435\u043c\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0447\u0435\u0440\u0435\u0437\u00a0<code>$(taskName.secureFilePath)<\/code>.<\/p>\n<p><code><strong>--force<\/strong><\/code>\u00a0\u0432 deliver.\u00a0\u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u0444\u043b\u0430\u0433\u0430 fastlane \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439\u00a0\u2014\u00a0\u0432 CI \u044d\u0442\u043e \u0432\u0435\u0448\u0430\u0435\u0442 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d.<\/p>\n<p><strong>\u0411\u0438\u043d\u0430\u0440\u043d\u0438\u043a \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0437\u0430 \u043e\u0434\u0438\u043d \u0432\u044b\u0437\u043e\u0432.<\/strong>\u00a0\u0418\u00a0<code>deliver<\/code>\u00a0(<code>--ipa<\/code>),\u00a0\u0438\u00a0<code>supply<\/code>\u00a0(<code>--aab<\/code>)\u00a0\u0443\u043c\u0435\u044e\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u043e\u0445\u043e\u0434\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u0431\u043e\u0440\u043a\u0443 \u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u043a\u0441\u0442\u044b.\u00a0\u0414\u043b\u044f iOS \u044d\u0442\u043e \u0435\u0449\u0451 \u0438 \u0432\u0430\u0436\u043d\u043e \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438:\u00a0<code>deliver<\/code>\u00a0\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0443\u00a0<em>\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0439<\/em>\u00a0\u0432\u0435\u0440\u0441\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.\u00a0\u041f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435 \u043f\u043e\u043b\u044f App Store\u00a0\u2014\u00a0name,\u00a0subtitle,\u00a0keywords,\u00a0description,\u00a0release notes\u00a0(\u00abWhat&#8217;s New\u00bb)\u00a0\u2014\u00a0\u043c\u043e\u0436\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044f \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0430\u00a0(\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u00a0\u2014\u00a0promotional text \u0438 \u043f\u0430\u0440\u0430 \u0441\u0441\u044b\u043b\u043e\u043a:\u00a0\u0438\u0445 Apple \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443 \u0436\u0438\u0432\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u043b\u044e\u0431\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442).\u00a0\u041f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f\u00a0<code>--ipa<\/code>\u00a0\u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u00a0<code>--app_version<\/code>,\u00a0\u043c\u044b \u043a\u0430\u043a \u0440\u0430\u0437 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0442\u0430\u043a\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e-\u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a,\u00a0\u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u0441\u0435 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0431\u0435\u0437 \u043e\u0448\u0438\u0431\u043e\u043a.\u00a0\u0415\u0441\u043b\u0438 \u0431\u044b \u043c\u044b \u0433\u0440\u0443\u0437\u0438\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435\u00a0(<code>--skip_binary_upload<\/code>)\u00a0\u0438 \u0447\u0435\u0440\u043d\u043e\u0432\u0438\u043a\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 \u043d\u0435 \u0431\u044b\u043b\u043e,\u00a0deliver \u0443\u043f\u0430\u043b \u0431\u044b \u043d\u0430 \u044d\u0442\u0438\u0445 \u043f\u043e\u043b\u044f\u0445.\u00a0\u0423 Google Play \u0442\u0430\u043a\u043e\u0433\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0435\u0442\u00a0\u2014\u00a0\u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u0430\u043c \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u0441\u0431\u043e\u0440\u043a\u0438,\u00a0\u043d\u043e \u0440\u0430\u0437 \u043c\u044b \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u043c \u0440\u0435\u043b\u0438\u0437,\u00a0\u0443\u0434\u043e\u0431\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043e\u0434\u043d\u0438\u043c \u0432\u044b\u0437\u043e\u0432\u043e\u043c\u00a0<code>supply<\/code>.\u00a0\u041f\u0440\u0438 \u0436\u0435\u043b\u0430\u043d\u0438\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 AAB \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443\u00a0(\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440,\u00a0<code>GooglePlayRelease@4<\/code>),\u00a0\u043e\u0441\u0442\u0430\u0432\u0438\u0432\u00a0<code>supply<\/code>\u00a0\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0444\u043b\u0430\u0433\u0430\u043c\u0438\u00a0<code>--skip_upload_aab --version_code $(VERSION_CODE)<\/code>.<\/p>\n<p><code><strong>--run_precheck_before_submit false<\/strong><\/code>\u00a0\u0432 deliver.\u00a0\u041e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u043e\u0442 Apple \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439\u00a0\u2014\u00a0\u043e\u043d\u0438 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u0435 \u0438 \u043d\u0435\u0440\u0435\u0434\u043a\u043e \u0434\u0430\u044e\u0442 \u043b\u043e\u0436\u043d\u044b\u0435 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u043d\u0435-\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0442\u0435\u043a\u0441\u0442\u0430\u0445.<\/p>\n<p><strong>\u0412\u0435\u0440\u0441\u0438\u044f \u043e\u0431\u0440\u0430\u0437\u0430 \u0430\u0433\u0435\u043d\u0442\u0430.<\/strong>\u00a0<code>macos-15<\/code>\u00a0\u0437\u0430\u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e\u00a0\u2014\u00a0\u044d\u0442\u043e \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e Xcode \u0438 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432.\u00a0\u041f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0443 \u0441\u0435\u0431\u044f \u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043d\u0430 \u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u043e\u0431\u0440\u0430\u0437 \u0438\u0437\u00a0<a href=\"https:\/\/learn.microsoft.com\/azure\/devops\/pipelines\/agents\/hosted\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 Microsoft<\/a>\u00a0\u2014\u00a0\u0441\u0442\u0430\u0440\u044b\u0435 \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c \u0432\u044b\u0432\u043e\u0434\u044f\u0442\u0441\u044f \u0438\u0437 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d23\/b50\/4d5\/d23b504d535772bf8c4bb987993eda8f.png\" width=\"1936\" height=\"1600\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d23\/b50\/4d5\/d23b504d535772bf8c4bb987993eda8f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d23\/b50\/4d5\/d23b504d535772bf8c4bb987993eda8f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u041f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438<\/h3>\n<p><strong>\u041b\u0438\u043c\u0438\u0442\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432.<\/strong>\u00a0\u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0441\u0432\u043e\u0439 \u043f\u0440\u0435\u0434\u0435\u043b:\u00a0App Store\u00a0\u2014\u00a0name \u0438 subtitle \u043f\u043e 30 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432,\u00a0keywords\u00a0\u2014\u00a0100;\u00a0Google Play\u00a0\u2014\u00a0title 30,\u00a0short description 80.\u00a0Fastlane \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u044d\u0442\u043e \u043d\u0430 \u0441\u0432\u043e\u0435\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0438 \u0432\u0435\u0440\u043d\u0451\u0442 \u043e\u0448\u0438\u0431\u043a\u0443 \u043f\u0440\u0438 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0438\u0438,\u00a0\u0442\u0430\u043a \u0447\u0442\u043e \u0431\u0438\u0442\u044b\u0439 \u0440\u0435\u043b\u0438\u0437 \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d \u043d\u0435 \u0443\u0435\u0434\u0435\u0442.\u00a0\u041c\u0438\u043d\u0443\u0441\u00a0\u2014\u00a0\u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u0435\u0442 \u0443\u0436\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438,\u00a0\u0432 \u0441\u0435\u0440\u0435\u0434\u0438\u043d\u0435 \u043f\u0440\u043e\u0433\u043e\u043d\u0430,\u00a0\u0438 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u0440\u0430\u0437\u0443 \u0432\u0438\u0434\u043d\u043e,\u00a0\u0432 \u043a\u0430\u043a\u043e\u0439 \u0438\u043c\u0435\u043d\u043d\u043e \u043b\u043e\u043a\u0430\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430.\u00a0\u0415\u0441\u043b\u0438 \u043b\u043e\u043a\u0430\u043b\u0435\u0439 \u043c\u043d\u043e\u0433\u043e,\u00a0\u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0434\u043b\u0438\u043d\u044b \u043f\u0440\u044f\u043c\u043e \u0432\u00a0<code>prepare_metadata.py<\/code>\u00a0\u2014\u00a0\u043e\u043d\u0430 \u0434\u0430\u0441\u0442 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0439 \u043e\u0442\u0447\u0451\u0442 \u0435\u0449\u0451 \u0434\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 fastlane.<\/p>\n<p><strong>\u041a\u043e\u0434\u044b \u043b\u043e\u043a\u0430\u043b\u0435\u0439 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430\u043c\u0438.<\/strong>\u00a0\u042d\u0442\u043e \u0441\u0430\u043c\u0430\u044f \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c.\u00a0\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0439:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u042f\u0437\u044b\u043a<\/p>\n<\/th>\n<th>\n<p align=\"left\">App Store<\/p>\n<\/th>\n<th>\n<p align=\"left\">Google Play<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0418\u0432\u0440\u0438\u0442<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>he<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>iw-IL<\/code>\u00a0(\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u043a\u043e\u0434)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0418\u0441\u043f\u0430\u043d\u0441\u043a\u0438\u0439 (\u041b\u0430\u0442\u0438\u043d\u0441\u043a\u0430\u044f \u0410\u043c\u0435\u0440\u0438\u043a\u0430)<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>es-MX<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>es-419<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041d\u043e\u0440\u0432\u0435\u0436\u0441\u043a\u0438\u0439<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>no<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>no-NO<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0421\u043b\u043e\u0432\u0435\u043d\u0441\u043a\u0438\u0439<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>sl-SI<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>sl<\/code><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u0432\u00a0<code>LOCALE_MAP<\/code>\u00a0\u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u044d\u0442\u0438 \u0441\u043b\u0443\u0447\u0430\u0438\u00a0\u2014\u00a0\u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043d\u0435 \u0437\u0430\u0431\u044b\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u043b\u043e\u043a\u0430\u043b\u044c \u0432 \u043e\u0431\u0430 \u043c\u0435\u0441\u0442\u0430:\u00a0STORES-\u0444\u0430\u0439\u043b \u0438 LOCALE_MAP.<\/p>\n<p><strong>Android-\u0444\u043e\u043b\u0431\u044d\u043a\u0438.<\/strong>\u00a0Google Play \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0440\u044f\u0434\u0430 \u043b\u043e\u043a\u0430\u043b\u0435\u0439\u00a0(\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440\u00a0<code>en-IN<\/code>,\u00a0<code>en-ZA<\/code>),\u00a0\u043f\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u0442 \u0441\u043c\u044b\u0441\u043b\u0430 \u0434\u0435\u043b\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0435\u0440\u0435\u0432\u043e\u0434.\u00a0<code>ANDROID_FALLBACKS<\/code>\u00a0\u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u044d\u0442\u043e:\u00a0\u0431\u0435\u0440\u0451\u0442 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0444\u0430\u0439\u043b\u0430.<\/p>\n<p><strong>\u041f\u0443\u0441\u0442\u043e\u0439\u00a0<\/strong><code><strong>What's New<\/strong><\/code><strong>\u00a0\u2014 \u0441\u0442\u0430\u0440\u044b\u0435 release notes \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0435.<\/strong>\u00a0\u0415\u0441\u043b\u0438 \u0443 \u043b\u043e\u043a\u0430\u043b\u0438 \u043d\u0435\u0442 \u0441\u0432\u043e\u0435\u0439 \u0441\u0435\u043a\u0446\u0438\u0438\u00a0<code>### What's New<\/code>,\u00a0\u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u0438\u0442 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 changelog \u0438\u0437\u00a0<a href=\"http:\/\/STORES.md\" rel=\"noopener noreferrer nofollow\"><code>STORES.md<\/code><\/a>\u00a0(\u0444\u043e\u043b\u0431\u044d\u043a).\u00a0\u041d\u043e \u0435\u0441\u043b\u0438 \u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c What&#8217;s New \u0432 \u0441\u0430\u043c\u043e\u043c\u00a0<a href=\"http:\/\/STORES.md\" rel=\"noopener noreferrer nofollow\"><code>STORES.md<\/code><\/a>,\u00a0\u043f\u0443\u0441\u0442\u044b\u043c\u0438 \u043e\u043a\u0430\u0436\u0443\u0442\u0441\u044f release notes \u0443\u0436\u0435 \u0432\u043e \u0432\u0441\u0435\u0445 \u043b\u043e\u043a\u0430\u043b\u044f\u0445\u00a0\u2014\u00a0fastlane \u0442\u043e\u0433\u0434\u0430 \u043d\u0435 \u0442\u0440\u043e\u043d\u0435\u0442 \u0442\u0435\u043a\u0441\u0442 \u0438 \u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439.\u00a0\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u043b\u0435\u0434\u0438\u0442\u0435,\u00a0\u0447\u0442\u043e\u0431\u044b \u0441\u0432\u0435\u0436\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u043e\u043f\u0430\u043b\u0430 \u0445\u043e\u0442\u044f \u0431\u044b \u0432 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0430\u0439\u043b.<\/p>\n<p><code><strong>api_key.json<\/strong><\/code><strong>\u00a0\u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e.<\/strong>\u00a0Apple \u043d\u0435 \u043e\u0442\u0434\u0430\u0451\u0442 \u043a\u043b\u044e\u0447 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 JSON\u00a0\u2014\u00a0\u043e\u043d\u0430 \u0434\u0430\u0451\u0442\u00a0<code>.p8<\/code>-\u0444\u0430\u0439\u043b \u043f\u043b\u044e\u0441 \u0434\u0432\u0430 ID \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435.\u00a0JSON \u0434\u043b\u044f fastlane \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e:\u00a0\u0432\u0437\u044f\u0442\u044c\u00a0<code>key_id<\/code>\u00a0\u0438\u00a0<code>issuer_id<\/code>\u00a0\u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b App Store Connect API,\u00a0\u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435\u00a0<code>.p8<\/code>\u00a0\u0432 \u043f\u043e\u043b\u0435\u00a0<code>key<\/code>\u00a0(\u0441\u00a0<code>\\n<\/code>\u00a0\u0432\u043c\u0435\u0441\u0442\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u043e\u0432 \u0441\u0442\u0440\u043e\u043a).<\/p>\n<p><code><strong>.p8<\/strong><\/code><strong>-\u043a\u043b\u044e\u0447 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437.<\/strong>\u00a0\u0415\u0441\u043b\u0438 \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u0438 \u0444\u0430\u0439\u043b\u00a0\u2014\u00a0\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0439\u0442\u0435 Secure File.\u00a0\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u0440\u0430\u0437\u0443 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c\u00a0<code>api_key.json<\/code>\u00a0\u0432 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u00a0(1Password,\u00a0Azure Key Vault)\u00a0\u2014\u00a0\u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 CI.<\/p>\n<hr\/>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u0418\u0442\u043e\u0433\u043e\u0432\u0430\u044f \u0441\u0445\u0435\u043c\u0430:<\/p>\n<pre><code>\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u0442 STORES-de.md         \u2193Merge \u0432 release         \u2193CI \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0431\u0438\u043d\u0430\u0440\u043d\u0438\u043a\u0438 \u0438 \u0441\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442 fastlane \u0447\u0435\u0440\u0435\u0437 Bundler (\u043a\u0435\u0448)         \u2193prepare_metadata.py \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 fastlane\/metadata\/         \u2193fastlane deliver + supply \u2192 \u043d\u043e\u0432\u044b\u0439 \u0440\u0435\u043b\u0438\u0437 \u0441\u043e \u0441\u0431\u043e\u0440\u043a\u043e\u0439 \u0438 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u043c\u0438                            \u0432 App Store Connect \u0438 Google Play<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438:<\/p>\n<ul>\n<li>\n<p><strong>\u0415\u0434\u0438\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438\u0441\u0442\u0438\u043d\u044b<\/strong>\u00a0\u2014 \u0432\u0435\u0441\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u043e\u0432 \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0445 Markdown-\u0444\u0430\u0439\u043b\u0430\u0445 \u043f\u043e\u0434 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0432\u0435\u0440\u0441\u0438\u0439<\/p>\n<\/li>\n<li>\n<p><strong>\u042d\u043a\u043e\u043d\u043e\u043c\u0438\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/strong>\u00a0\u2014 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f 20+ \u044f\u0437\u044b\u043a\u043e\u0432 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b\u043e\u0441\u044c \u0441 \u0447\u0430\u0441\u0430 \u0440\u0443\u0447\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u043e \u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430 \u0438 ~10 \u043c\u0438\u043d\u0443\u0442 CI<\/p>\n<\/li>\n<li>\n<p><strong>\u0427\u0438\u0442\u0430\u0435\u043c\u044b\u0435 \u0434\u0438\u0444\u0444\u044b<\/strong>\u00a0\u2014 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0432\u0438\u0434\u043d\u043e \u043a\u0430\u043a \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0432 PR<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0421\u0441\u044b\u043b\u043a\u0438:<\/strong><\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/docs.fastlane.tools\/actions\/deliver\/\" rel=\"noopener noreferrer nofollow\">fastlane deliver<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/docs.fastlane.tools\/actions\/supply\/\" rel=\"noopener noreferrer nofollow\">fastlane supply<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/appstoreconnectapi\" rel=\"noopener noreferrer nofollow\">App Store Connect API<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/developers.google.com\/android-publisher\" rel=\"noopener noreferrer nofollow\">Google Play Developer API<\/a><\/p>\n<\/li>\n<\/ul>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1062986\/\">https:\/\/habr.com\/ru\/articles\/1062986\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 20+\u00a0\u044f\u0437\u044b\u043a\u043e\u0432.\u00a0\u041a\u0430\u0436\u0434\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u00a0\u2014\u00a0\u044d\u0442\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u0430\u044f \u0441\u0431\u043e\u0440\u043a\u0430,\u00a0\u043d\u043e \u0438 \u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u043e\u0431\u043e\u0438\u0445 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430\u0445:\u00a0\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435,\u00a0\u043f\u043e\u0434\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a,\u00a0\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435,\u00a0\u00ab\u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb.\u00a0\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044f\u0437\u044b\u043a\u0430,\u00a0\u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0435,\u00a0\u0447\u0435\u0440\u0435\u0437 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u0439 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.\u041f\u0440\u0438 \u0434\u0432\u0443\u0445 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u0445 \u044f\u0437\u044b\u043a\u043e\u0432 \u0438 \u0434\u0432\u0443\u0445 \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430\u0445 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u00a0\u00ab\u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb\u00a0\u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 30\u201340 \u043c\u0438\u043d\u0443\u0442.\u00a0\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043b\u0435\u0433\u043a\u043e \u043e\u0448\u0438\u0431\u0438\u0442\u044c\u0441\u044f:\u00a0\u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0435 \u0442\u0443 \u0432\u0435\u0440\u0441\u0438\u044e \u0442\u0435\u043a\u0441\u0442\u0430,\u00a0\u0437\u0430\u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d \u044f\u0437\u044b\u043a,\u00a0\u043f\u0435\u0440\u0435\u043f\u0443\u0442\u0430\u0442\u044c \u043f\u043e\u043b\u044f \u043c\u0435\u0436\u0434\u0443 iOS \u0438 Android.\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u043f\u043e\u043a\u0430\u0436\u0443,\u00a0\u043a\u0430\u043a \u0441\u0432\u0435\u0441\u0442\u0438 \u0432\u0441\u0451 \u044d\u0442\u043e \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u043a\u043e\u043c\u043c\u0438\u0442\u0443.Fastlane \u0432 \u0434\u0432\u0443\u0445 \u0441\u043b\u043e\u0432\u0430\u0445fastlane\u00a0\u2014\u00a0Ruby-\u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0433\u043e CI\/CD.\u00a0\u0414\u0432\u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442:deliver\u00a0\u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 App Store Connectsupply\u00a0\u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 Google Play\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f:\u00a0\u043e\u0431\u0430 \u0447\u0438\u0442\u0430\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435\u00a0\u0438\u0437 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0430 \u0434\u0438\u0441\u043a\u0435,\u00a0\u0430 \u043d\u0435 \u0438\u0437 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430.\u00a0\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435\u00a0.txt-\u0444\u0430\u0439\u043b\u044b \u0432 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043f\u043a\u0438\u00a0\u2014\u00a0\u0438 fastlane \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435.\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c fastlane \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e.\u00a0\u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u00a0Gemfile\u00a0\u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438\u00a0\u2014\u00a0CI \u0441\u043a\u0430\u0447\u0430\u0435\u0442 \u0432\u0441\u0451 \u043d\u0443\u0436\u043d\u043e\u0435 \u0441\u0430\u043c \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435:# Gemfilesource &#171;https:\/\/rubygems.org&#187;gem &#171;fastlane&#187;, &#171;~&gt; 2.235&#8243;# \u042f\u0432\u043d\u043e \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u043d\u0437\u0438\u0442\u0438\u0432\u043d\u0443\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0441\u0431\u043e\u0435\u0432 \u043d\u0430 CI-\u0430\u0433\u0435\u043d\u0442\u0430\u0445gem &#171;multi_json&#187;, &#171;&gt;= 1.14.1&#8243;\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430: \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b \u043d\u0430 \u044f\u0437\u044b\u043aFastlane \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0434\u0435\u0440\u0435\u0432\u0430\u00a0.txt-\u0444\u0430\u0439\u043b\u043e\u0432:fastlane\/metadata\/  ios\/    en-US\/      name.txt      subtitle.txt      keywords.txt      promotional_text.txt      description.txt      release_notes.txt    de-DE\/      &#8230;  android\/    en-US\/      title.txt      short_description.txt      full_description.txt      changelogs\/        default.txt    de-DE\/      &#8230;\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e:\u00a0\u043c\u043d\u043e\u0433\u043e \u043c\u0435\u043b\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432,\u00a0\u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439 diff \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445,\u00a0\u043b\u0435\u0433\u043a\u043e \u0441\u0431\u0438\u0442\u044c\u0441\u044f \u0441 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u043e\u043c \u043a\u043e\u0434\u043e\u0432 \u043b\u043e\u043a\u0430\u043b\u0435\u0439.\u0420\u0435\u0448\u0435\u043d\u0438\u0435:\u00a0\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u043a\u0441\u0442\u044b \u0432 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0445 Markdown-\u0444\u0430\u0439\u043b\u0430\u0445\u00a0\u2014\u00a0\u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u043d\u0430 \u044f\u0437\u044b\u043a.\u00a0Python-\u0441\u043a\u0440\u0438\u043f\u0442 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0438\u0437 \u043d\u0438\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0434\u043b\u044f fastlane \u043f\u0440\u044f\u043c\u043e \u0432 CI \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.\u00a0\u041f\u0430\u043f\u043a\u0430\u00a0fastlane\/metadata\/\u00a0\u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f\u00a0\u2014\u00a0\u044d\u0442\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442 \u0441\u0431\u043e\u0440\u043a\u0438,\u00a0\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0438 \u0438\u0441\u0447\u0435\u0437\u0430\u0435\u0442 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0430\u0433\u0435\u043d\u0442\u043e\u043c.stores\/               \u2190 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0435 \u0442\u0435\u043a\u0441\u0442\u044b, \u0432\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u0432 git  STORES.md           \u2190 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 (\u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439, en-US)  STORES-de.md  STORES-fr-FR.md  STORES-ja.md  STORES-ru.md  &#8230;scripts\/  prepare_metadata.py \u2190 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 fastlane\/metadata\/ \u0438\u0437 stores\/Gemfile               \u2190 fastlane \u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f CI\u0424\u043e\u0440\u043c\u0430\u0442 STORES-\u0444\u0430\u0439\u043b\u0430\u042d\u0442\u043e \u043d\u0435 \u043a\u0430\u043a\u043e\u0439 \u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u0430 \u043f\u0440\u0438\u0434\u0443\u043c\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0434 \u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u0444\u043e\u0440\u043c\u0430\u0442. \u041a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0435\u043a\u0446\u0438\u0438 \u0441 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c\u0438.\u00a0\u041a\u043e\u043d\u0442\u0435\u043d\u0442\u00a0\u2014\u00a0\u0432 \u0431\u043b\u043e\u043a\u0430\u0445\u00a0&#171;`text\u00a0(\u0447\u0442\u043e\u0431\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u043d\u0435 \u043c\u0435\u0448\u0430\u043b\u0438 \u0438 \u043b\u0438\u043c\u0438\u0442\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0431\u044b\u043b\u0438 \u0432\u0438\u0434\u043d\u044b \u043f\u0440\u044f\u043c\u043e \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435).\u00a0\u0412 \u043a\u043e\u043d\u0446\u0435 \u0444\u0430\u0439\u043b\u0430\u00a0\u2014\u00a0\u0441\u0435\u043a\u0446\u0438\u044f\u00a0\u00ab\u0427\u0442\u043e \u043d\u043e\u0432\u043e\u0433\u043e\u00bb\u00a0\u0441 \u0438\u0441\u0442\u043e\u0440\u0438\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0439,\u00a0\u043d\u043e\u0432\u0435\u0439\u0448\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043f\u0435\u0440\u0432\u0430\u044f:&lt;!&#8212; Language: German &#8212;&gt;### Title&#171;`textMeineApp &#8212; Kurzer Slogan&#171;`### Appstore keywords (100 characters max)&#171;`textproduktivit\u00e4t,aufgaben,planung,timer,fokus,notizen,erinnerung,kalender&#171;`### Appstore subtitle (30 characters max)&#171;`textAufgaben einfach erledigen&#171;`### Appstore promotional text (170 characters max)&#171;`textJetzt mit neuen Widgets f\u00fcr den Startbildschirm!Behalte deine Aufgaben immer im Blick.&#171;`### Google Play short description (80 characters max)&#171;`textEinfache Aufgabenverwaltung mit Timer und Erinnerungen.&#171;`### Full description (Google Play and AppStore)&#171;`textMeineApp hilft dir, den \u00dcberblick \u00fcber deine Aufgaben zu behalten.FUNKTIONEN- Aufgaben mit F\u00e4lligkeitsdatum und Priorit\u00e4t- Integrierter Fokus-Timer- Erinnerungen und Benachrichtigungen- Widgets f\u00fcr den Startbildschirm- Synchronisierung zwischen Ger\u00e4ten&#171;`### What&#8217;s New#### 2.4Neue Widgets f\u00fcr den Startbildschirm. Performance-Verbesserungen.#### 2.3Fehlerbehebungen und Stabilit\u00e4tsverbesserungen.\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u043e\u0434\u043d\u0438\u043c \u0444\u0430\u0439\u043b\u043e\u043c \u043d\u0430 \u044f\u0437\u044b\u043a.\u00a0git diff\u00a0\u043d\u0430 PR \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0440\u043e\u0432\u043d\u043e \u0442\u043e,\u00a0\u0447\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c.\u00a0\u041f\u0435\u0440\u0435\u0434 \u0440\u0435\u043b\u0438\u0437\u043e\u043c\u00a0\u2014\u00a0\u0434\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0448\u044c \u043d\u043e\u0432\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c\u00a0#### 2.5\u00a0\u0432\u0432\u0435\u0440\u0445 \u0432\u00a0What&#8217;s New.\u0421\u043a\u0440\u0438\u043f\u0442: \u0438\u0437 Markdown \u0432 fastlane\u0421\u043a\u0440\u0438\u043f\u0442\u00a0scripts\/prepare_metadata.py\u00a0\u0447\u0438\u0442\u0430\u0435\u0442 \u0432\u0441\u0435\u00a0STORES-*.md,\u00a0\u043f\u0430\u0440\u0441\u0438\u0442 \u0441\u0435\u043a\u0446\u0438\u0438 \u0438 \u043f\u0438\u0448\u0435\u0442\u00a0.txt-\u0444\u0430\u0439\u043b\u044b \u0432 \u043d\u0443\u0436\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443.\u00a0\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0432 CI \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u0432 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u044b.#!\/usr\/bin\/env python3&#8243;&#187;&#187;prepare_metadata.py \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 fastlane\/metadata\/ \u0438\u0437 stores\/STORES-*.md.\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435:    python scripts\/prepare_metadata.py [&#8212;dry-run]\u0421\u043e\u0437\u0434\u0430\u0451\u0442:    fastlane\/metadata\/ios\/{locale}\/        name.txt, subtitle.txt, keywords.txt,        promotional_text.txt, description.txt, release_notes.txt    fastlane\/metadata\/android\/{locale}\/        title.txt, short_description.txt, full_description.txt,        changelogs\/default.txt&#187;&#187;&#187;import reimport sysfrom pathlib import PathROOT       = Path(__file__).resolve().parent.parentSTORES_DIR = ROOT \/ &#171;stores&#187;IOS_META   = ROOT \/ &#171;fastlane&#187; \/ &#171;metadata&#187; \/ &#171;ios&#187;AND_META   = ROOT \/ &#171;fastlane&#187; \/ &#171;metadata&#187; \/ &#171;android&#187;DRY_RUN = &#171;&#8212;dry-run&#187; in sys.argv# \u041c\u0430\u043f\u043f\u0438\u043d\u0433: \u043a\u043b\u044e\u0447 \u0444\u0430\u0439\u043b\u0430 \u2192 (ios_locale | None, android_locale | None)# None \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u044f\u0437\u044b\u043a \u2014 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u043c.# \u041a\u043b\u044e\u0447 &#171;&#187; \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 STORES.md (\u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, en-US).LOCALE_MAP: dict[str, tuple[str | None, str | None]] = {    &#171;&#187;:        (&#171;en-US&#187;,   &#171;en-US&#187;),    &#171;ar&#187;:      (&#171;ar-SA&#187;,   &#171;ar&#187;),    &#171;bg&#187;:      (None,      &#171;bg&#187;),        # App Store \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 bg    &#171;cs&#187;:      (&#171;cs&#187;,      &#171;cs-CZ&#187;),    &#171;da&#187;:      (&#171;da&#187;,      &#171;da-DK&#187;),    &#171;de&#187;:      (&#171;de-DE&#187;,   &#171;de-DE&#187;),    &#171;el&#187;:      (&#171;el&#187;,      &#171;el-GR&#187;),    &#171;en-AU&#187;:   (&#171;en-AU&#187;,   &#171;en-AU&#187;),    &#171;en-CA&#187;:   (&#171;en-CA&#187;,   &#171;en-CA&#187;),    &#171;en-GB&#187;:   (&#171;en-GB&#187;,   &#171;en-GB&#187;),    &#171;es-ES&#187;:   (&#171;es-ES&#187;,   &#171;es-ES&#187;),    &#171;es-MX&#187;:   (&#171;es-MX&#187;,   &#171;es-419&#187;),   # Google Play: es-419 \u0434\u043b\u044f \u041b\u0430\u0442\u0438\u043d\u0441\u043a\u043e\u0439 \u0410\u043c\u0435\u0440\u0438\u043a\u0438    &#171;fi&#187;:      (&#171;fi&#187;,      &#171;fi-FI&#187;),    &#171;fr-CA&#187;:   (&#171;fr-CA&#187;,   &#171;fr-CA&#187;),    &#171;fr-FR&#187;:   (&#171;fr-FR&#187;,   &#171;fr-FR&#187;),    &#171;he&#187;:      (&#171;he&#187;,      &#171;iw-IL&#187;),    # Google Play \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u043a\u043e\u0434 iw    &#171;hi&#187;:      (&#171;hi&#187;,      &#171;hi-IN&#187;),    &#171;hr&#187;:      (&#171;hr&#187;,      &#171;hr&#187;),    &#171;hu&#187;:      (&#171;hu&#187;,      &#171;hu-HU&#187;),    &#171;id&#187;:      (&#171;id&#187;,      &#171;id&#187;),    &#171;it&#187;:      (&#171;it&#187;,      &#171;it-IT&#187;),    &#171;ja&#187;:      (&#171;ja&#187;,      &#171;ja-JP&#187;),    &#171;ko&#187;:      (&#171;ko&#187;,      &#171;ko-KR&#187;),    &#171;nl&#187;:      (&#171;nl-NL&#187;,   &#171;nl-NL&#187;),    &#171;no&#187;:      (&#171;no&#187;,      &#171;no-NO&#187;),    # App Store: &#171;no&#187;, Google Play: &#171;no-NO&#187;    &#171;pl&#187;:      (&#171;pl&#187;,      &#171;pl-PL&#187;),    &#171;pt-BR&#187;:   (&#171;pt-BR&#187;,   &#171;pt-BR&#187;),    &#171;pt-PT&#187;:   (&#171;pt-PT&#187;,   &#171;pt-PT&#187;),    &#171;ro&#187;:      (&#171;ro&#187;,      &#171;ro&#187;),    &#171;ru&#187;:      (&#171;ru&#187;,      &#171;ru-RU&#187;),    &#171;sv&#187;:      (&#171;sv&#187;,      &#171;sv-SE&#187;),    &#171;th&#187;:      (&#171;th&#187;,      &#171;th&#187;),    &#171;tr&#187;:      (&#171;tr&#187;,      &#171;tr-TR&#187;),    &#171;uk&#187;:      (&#171;uk&#187;,      &#171;uk&#187;),    &#171;vi&#187;:      (&#171;vi&#187;,      &#171;vi&#187;),    &#171;zh&#187;:      (&#171;zh-Hans&#187;, &#171;zh-CN&#187;),    &#171;zh-Hant&#187;: (&#171;zh-Hant&#187;, &#171;zh-TW&#187;),}# \u041b\u043e\u043a\u0430\u043b\u0438 Google Play \u0431\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e STORES-\u0444\u0430\u0439\u043b\u0430.# \u0411\u0435\u0440\u0451\u043c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0444\u0430\u0439\u043b\u0430.ANDROID_FALLBACKS: dict[str, str] = {    &#171;en-IN&#187;: &#171;&#187;,         # English (India) \u2192 en-US    &#171;en-SG&#187;: &#171;&#187;,         # English (Singapore) \u2192 en-US    &#171;en-ZA&#187;: &#171;&#187;,         # English (South Africa) \u2192 en-US    &#171;es-US&#187;: &#171;es-MX&#187;,   # Spanish (United States) \u2192 es-MX    &#171;zh-HK&#187;: &#171;zh-Hant&#187;, # Chinese (Hong Kong) \u2192 zh-Hant}def extract_section(text: str, heading: str) -&gt; str:    &#171;&#187;&#187;\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0431\u043b\u043e\u043a\u0430 &#171;`text &#8230; &#171;` \u043f\u043e\u0434 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c heading.&#187;&#187;&#187;    pattern = rf&#187;### {re.escape(heading)}[^\\n]*\\n&#171;`text\\n(.*?)&#171;`&#187;    m = re.search(pattern, text, re.DOTALL)    return m.group(1).strip() if m else &#171;&#187;def extract_whatsnew(text: str) -&gt; str:    &#171;&#187;&#187;\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u0435\u043a\u0441\u0442 \u043f\u0435\u0440\u0432\u043e\u0439 (\u043d\u043e\u0432\u0435\u0439\u0448\u0435\u0439) \u0437\u0430\u043f\u0438\u0441\u0438 \u0438\u0437 \u0441\u0435\u043a\u0446\u0438\u0438 ### What&#8217;s New.&#187;&#187;&#187;    section_m = re.search(r&#187;### What&#8217;s New\\n(.*?)(?=\\n### |\\Z)&#187;, text, re.DOTALL)    if not section_m:        return &#171;&#187;    section = section_m.group(1)    # \u0420\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u043c \u043f\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u043c \u0432\u0435\u0440\u0441\u0438\u0439 \u0438 \u0431\u0435\u0440\u0451\u043c \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u043b\u043e\u043a    parts = re.split(r&#187;^#### .+$&#187;, section, flags=re.MULTILINE)    return parts[1].strip() if len(parts) &gt;= 2 else &#171;&#187;def parse_stores_file(path: Path) -&gt; dict[str, str]:    content = path.read_text(encoding=&#187;utf-8&#187;)    return {        &#171;title&#187;:       extract_section(content, &#171;Title&#187;),        &#171;keywords&#187;:    extract_section(content, &#171;Appstore keywords&#187;),        &#171;subtitle&#187;:    extract_section(content, &#171;Appstore subtitle&#187;),        &#171;promo&#187;:       extract_section(content, &#171;Appstore promotional text&#187;),        &#171;short_desc&#187;:  extract_section(content, &#171;Google Play short description&#187;),        &#171;description&#187;: extract_section(content, &#171;Full description&#187;),        &#171;whatsnew&#187;:    extract_whatsnew(content),    }def write_file(path: Path, content: str) -&gt; None:    if DRY_RUN:        print(f&#187;  [dry] {path}&#187;)        return    path.parent.mkdir(parents=True, exist_ok=True)    path.write_text(content + &#171;\\n&#187;, encoding=&#187;utf-8&#8243;)def write_ios(locale: str, data: dict[str, str]) -&gt; None:    base = IOS_META \/ locale    fields = {        &#171;name.txt&#187;:             data[&#171;title&#187;],        &#171;subtitle.txt&#187;:         data[&#171;subtitle&#187;],        &#171;keywords.txt&#187;:         data[&#171;keywords&#187;],        &#171;promotional_text.txt&#187;: data[&#171;promo&#187;],        &#171;description.txt&#187;:      data[&#171;description&#187;],        &#171;release_notes.txt&#187;:    data[&#171;whatsnew&#187;],    }    for filename, content in fields.items():        if content:            write_file(base \/ filename, content)def write_android(locale: str, data: dict[str, str]) -&gt; None:    base = AND_META \/ locale    fields = {        &#171;title.txt&#187;:             data[&#171;title&#187;],        &#171;short_description.txt&#187;: data[&#171;short_desc&#187;],        &#171;full_description.txt&#187;:  data[&#171;description&#187;],    }    for filename, content in fields.items():        if content:            write_file(base \/ filename, content)    if data[&#171;whatsnew&#187;]:        write_file(base \/ &#171;changelogs&#187; \/ &#171;default.txt&#187;, data[&#171;whatsnew&#187;])def stores_key(path: Path) -&gt; str:    &#171;&#187;&#187;STORES-fr-FR.md \u2192 &#8216;fr-FR&#8217;, STORES.md \u2192 &#187;&#187;&#187;&#187;    stem = path.stem    return &#171;&#187; if stem == &#171;STORES&#187; else stem[len(&#171;STORES-&#171;):]def main() -&gt; None:    store_files  = sorted(STORES_DIR.glob(&#171;STORES*.md&#187;))    parsed_cache: dict[str, dict[str, str]] = {}    unmapped: list[str] = []    # \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0430\u0440\u0441\u0438\u043c \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b, \u0447\u0442\u043e\u0431\u044b en-US \u043c\u043e\u0433 \u0441\u0442\u0430\u0442\u044c \u0444\u043e\u043b\u0431\u044d\u043a\u043e\u043c \u0434\u043b\u044f What&#8217;s New    for path in store_files:        key = stores_key(path)        if key not in LOCALE_MAP:            unmapped.append(key)            continue        parsed_cache[key] = parse_stores_file(path)    # \u0410\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 changelog \u0438\u0437 STORES.md \u2014&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-488549","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/488549","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=488549"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/488549\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=488549"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=488549"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=488549"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}