scratch-l10n 6.1.93 → 6.1.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/editor/interface/hy.json +63 -63
- package/locales/editor-msgs.js +63 -63
- package/locales/interface-msgs.js +63 -63
- package/package.json +5 -5
- package/scripts/lib/errors.mts +12 -0
- package/scripts/lib/freshdesk-api.mts +70 -14
- package/scripts/lib/help-utils.mts +74 -44
- package/scripts/lib/transifex.mts +159 -11
- package/scripts/lib/warnings.mts +26 -0
- package/scripts/tx-pull-help-names.mts +3 -1
- package/scripts/tx-push-help.mts +63 -11
- package/www/scratch-website.general-l10njson/hy.json +1 -1
|
@@ -20565,14 +20565,14 @@ export default {
|
|
|
20565
20565
|
"gui.unsupportedBrowser.back": "Հետ գնալ",
|
|
20566
20566
|
"gui.unsupportedBrowser.previewfaq": "Ավելին իմանալու համար այցելեք <a>{previewFaqLink}</a>։",
|
|
20567
20567
|
"gui.unsupportedBrowser.previewfaqlinktext": "ՀՏՀ",
|
|
20568
|
-
"gui.cards.tutorialsIcon": "
|
|
20569
|
-
"gui.cards.shrinkIcon": "
|
|
20570
|
-
"gui.cards.expandIcon": "
|
|
20571
|
-
"gui.cards.closeIcon": "
|
|
20572
|
-
"gui.cards.leftArrowIcon": "
|
|
20573
|
-
"gui.cards.rightArrowIcon": "
|
|
20574
|
-
"gui.cards.previousStepButton": "
|
|
20575
|
-
"gui.cards.nextStepButton": "
|
|
20568
|
+
"gui.cards.tutorialsIcon": "Ուսուցողական նյութերի մանրապատկեր",
|
|
20569
|
+
"gui.cards.shrinkIcon": "Փոքրացնել մանրապատկերը",
|
|
20570
|
+
"gui.cards.expandIcon": "Մեծացնել մանրապատկերը",
|
|
20571
|
+
"gui.cards.closeIcon": "Փակել մանրապատկերը",
|
|
20572
|
+
"gui.cards.leftArrowIcon": "Ձախ սլաքի պատկերը",
|
|
20573
|
+
"gui.cards.rightArrowIcon": "Աջ սլաքի պատկերը",
|
|
20574
|
+
"gui.cards.previousStepButton": "Նախորդ քայլը",
|
|
20575
|
+
"gui.cards.nextStepButton": "Հաջորդ քայլը",
|
|
20576
20576
|
"gui.cards.all-tutorials": "Ձեռնարկներ",
|
|
20577
20577
|
"gui.cards.shrink": "Փոքրացնել",
|
|
20578
20578
|
"gui.cards.expand": "Ընդլայնել",
|
|
@@ -20582,8 +20582,8 @@ export default {
|
|
|
20582
20582
|
"gui.comingSoon.message1": "Մի անհանգստացեք, մենք աշխատում ենք դրա վրա {emoji}",
|
|
20583
20583
|
"gui.comingSoon.message2": "Շուտով...",
|
|
20584
20584
|
"gui.comingSoon.message3": "Մենք աշխատում ենք դրա վրա {emoji}",
|
|
20585
|
-
"gui.confirmationPrompt.confirm": "
|
|
20586
|
-
"gui.confirmationPrompt.cancel": "
|
|
20585
|
+
"gui.confirmationPrompt.confirm": "այո",
|
|
20586
|
+
"gui.confirmationPrompt.cancel": "ոչ",
|
|
20587
20587
|
"gui.connection.auto-scanning.prescan": "Մոտեցրեք սարքը և սկսեք որոնումը:",
|
|
20588
20588
|
"gui.connection.auto-scanning.scanBeginMessage": "Սեղմեք սարքի վրայի կոճակը։",
|
|
20589
20589
|
"gui.connection.auto-scanning.noPeripheralsFound": "Ոչ մի սարք չի գտնվել",
|
|
@@ -20633,8 +20633,8 @@ export default {
|
|
|
20633
20633
|
"gui.customProcedures.cancel": "Չեղարկել",
|
|
20634
20634
|
"gui.customProcedures.ok": "Լավ",
|
|
20635
20635
|
"gui.debugModal.title": "Վրիպազերծում | Լուծել խնդիրները",
|
|
20636
|
-
"gui.debugModal.previous": "
|
|
20637
|
-
"gui.debugModal.next": "
|
|
20636
|
+
"gui.debugModal.previous": "Նախորդը",
|
|
20637
|
+
"gui.debugModal.next": "Հաջորդը",
|
|
20638
20638
|
"gui.debugModal.readAloud.title": "Բարձրաձայն կարդալ",
|
|
20639
20639
|
"gui.debugModal.readAloud.description1": "Կոդը բարձրաձայն կարդալիս մտածեք համակարգչի տեսանկյունից։",
|
|
20640
20640
|
"gui.debugModal.readAloud.description2": "Արդյո՞ք ներառում եք քայլեր, որոնք իրականում չկան։",
|
|
@@ -20660,67 +20660,67 @@ export default {
|
|
|
20660
20660
|
"gui.debugModal.tinkerWithBlockOrder.description5": "Փորձեք մասնիկներն օգտագործել ցիկլի կամ պայմանական հրահանգի ներսում՝ դրանք ցիկլից կամ պայմանական հրահանգից դուրս օգտագործելու փոխարեն։",
|
|
20661
20661
|
"gui.debugModal.toLoopOrNot.title": "Կիրառե՞լ ցիկլ, թե՞ ոչ",
|
|
20662
20662
|
"gui.debugModal.toLoopOrNot.description1": "Եթե օգտագործում եք «Կառավարում» բաժնի այնպիսի մասնիկներ, ինչպիսիք են «անվերջ» և «կրկնել», ստուգեք՝ արդյո՞ք ցիկլի ներսում գտնվող բոլոր մասնիկները պետք է այնտեղ լինեն, կամ արդյո՞ք որևէ մասնիկ (օրինակ՝ «սպասել») բացակայում է՝ գործողությունը վերակայելու կամ ժամանակը կարգավորելու համար։ Ցանկանո՞ւմ եք, որ ձեր ցիկլն անվերջ գործարկվի, թե՞ որոշակի քանակով։ Արդյո՞ք որևէ բան պետք է դադարեցնի ցիկլը։",
|
|
20663
|
-
"gui.debugModal.toLoopOrNot.description2": "
|
|
20664
|
-
"gui.debugModal.timingAndParallelism.title": "
|
|
20665
|
-
"gui.debugModal.timingAndParallelism.sectionTitle": "
|
|
20666
|
-
"gui.debugModal.timingAndParallelism.description1": "
|
|
20667
|
-
"gui.debugModal.timingAndParallelism.description2": "
|
|
20668
|
-
"gui.debugModal.thinkAboutBlockOptions.title": "
|
|
20663
|
+
"gui.debugModal.toLoopOrNot.description2": "Հնարավոր է, որ ցիկլ չես օգտագործում այն դեպքում, երբ պետք է։ Օրինակ՝ եթե օգտագործում ես «եթե, ապա» պայմանի մասնիկն, արդյո՞ք ծրագիրը պետք է միայն մեկ անգամ ստուգի՝ պայմանը ճիշտ է, թե սխալ։ Թե՞ այն պետք է անընդհատ ստուգի, որի դեպքում պայմանի մասնիկը պետք է տեղադրել «անվերջ» ցիկլի ներսում։",
|
|
20664
|
+
"gui.debugModal.timingAndParallelism.title": "Մտածիր գործողությունների ժամանակի և զուգահեռ կատարման մասին",
|
|
20665
|
+
"gui.debugModal.timingAndParallelism.sectionTitle": "Ժամանակ և զուգահեռ կատարում",
|
|
20666
|
+
"gui.debugModal.timingAndParallelism.description1": "Ունե՞ս մի քանի իրադարձություններ, որոնք պետք է կատարվեն միաժամանակ։ Եթե երկու գործողությունների հաջորդականություն ծրագրավորված է միաժամանակ սկսվելու համար, կարող ես ծրագրային անկանխատեսելի դրսևորում ստանալ։",
|
|
20667
|
+
"gui.debugModal.timingAndParallelism.description2": "Ավելացրու կարճ դադարներ, հաղորդագրություններ կամ օգտագործողի գործողություններ (օրինակ՝ սեղմում կամ ստեղնի հպում)՝ տեսնելու համար, թե արդյոք դրանք ազդում են արդյունքի վրա։",
|
|
20668
|
+
"gui.debugModal.thinkAboutBlockOptions.title": "Մտածիր մասնիկի տարբերակների մասին",
|
|
20669
20669
|
"gui.debugModal.thinkAboutBlockOptions.description1": "Is there a similar but different block you can use?",
|
|
20670
|
-
"gui.debugModal.thinkAboutBlockOptions.description2": "
|
|
20671
|
-
"gui.debugModal.thinkAboutBlockOptions.description3": "
|
|
20672
|
-
"gui.debugModal.checkTheValues.title": "
|
|
20673
|
-
"gui.debugModal.checkTheValues.description1": "
|
|
20674
|
-
"gui.debugModal.checkTheValues.description2": "
|
|
20675
|
-
"gui.debugModal.checkTheValues.description3": "
|
|
20676
|
-
"gui.debugModal.checkCodeSequence.title": "
|
|
20677
|
-
"gui.debugModal.checkCodeSequence.description1": "
|
|
20678
|
-
"gui.debugModal.checkCodeSequence.description2": "
|
|
20679
|
-
"gui.debugModal.checkCodeSequence.description3": "
|
|
20680
|
-
"gui.debugModal.commentYourCode.title": "
|
|
20681
|
-
"gui.debugModal.commentYourCode.description1": "
|
|
20682
|
-
"gui.debugModal.commentYourCode.description2": "
|
|
20683
|
-
"gui.debugModal.takeABreak.title": "
|
|
20684
|
-
"gui.debugModal.takeABreak.description1": "
|
|
20685
|
-
"gui.debugModal.takeABreak.description2": "
|
|
20686
|
-
"gui.debugModal.askForHelp.title": "
|
|
20687
|
-
"gui.debugModal.askForHelp.description1": "
|
|
20688
|
-
"gui.debugModal.askForHelp.description2": "
|
|
20689
|
-
"gui.gui.shouldDeleteSprite": "
|
|
20690
|
-
"gui.gui.shouldDeleteCostume": "
|
|
20691
|
-
"gui.gui.shouldDeleteSound": "
|
|
20692
|
-
"gui.gui.deleteAssetHeading": "
|
|
20670
|
+
"gui.debugModal.thinkAboutBlockOptions.description2": "Որոշ մասնիկներ նման են իրար, բայց կարող են տարբեր կերպ գործել, օրինակ՝ «սահմանել» և «փոխել», կամ «արտաբերել մինչև ավարտը» և «արտաբերել»։",
|
|
20671
|
+
"gui.debugModal.thinkAboutBlockOptions.description3": "Փորձիր քո օգտագործած մասնիկի փոխարեն կիրառել մեկ այլ նմանատիպ մասնիկ և տես՝ արդյոք դա ազդում է արդյունքի վրա։",
|
|
20672
|
+
"gui.debugModal.checkTheValues.title": "Ստուգիր արժեքները",
|
|
20673
|
+
"gui.debugModal.checkTheValues.description1": "Եթե օգտագործում ես փոփոխականներ կամ արժեք վերադարձնող մասնիկներ, ստուգիր դրանց արժեքը կոդի հաջորդականության կատարման պահին։",
|
|
20674
|
+
"gui.debugModal.checkTheValues.description2": "Արդյո՞ք բոլոր կերպարները պետք է կառավարեն փոփոխականը, թե՞ այն պետք է կառավարի միայն մեկ կերպար։",
|
|
20675
|
+
"gui.debugModal.checkTheValues.description3": "Որտե՞ղ է արժեքը վերակայվում։ Որտե՞ղ է այն փոխվում։",
|
|
20676
|
+
"gui.debugModal.checkCodeSequence.title": "Ստուգիր կոդի հաջորդականությունը",
|
|
20677
|
+
"gui.debugModal.checkCodeSequence.description1": "Անհրաժեշտության դեպքում ստուգիր, արդյո՞ք սցենարը կցված է ճիշտ կերպարին կամ ետնապատկերին։",
|
|
20678
|
+
"gui.debugModal.checkCodeSequence.description2": "Եթե պետք է կոդը տեղափոխիր մեկ այլ կերպարի վրա․ սեղմիր ու քաշիր այն դեպի համապատասխան կերպարը։ Բաց թող այն, երբ կերպարը սկսի շարժվել։",
|
|
20679
|
+
"gui.debugModal.checkCodeSequence.description3": "Կարող ես նաև օգտագործել քո Ուսապարկը (էկրանի ներքևում)՝ կոդը կամ այլ նյութերը պահելու և տեղափոխելու համար։",
|
|
20680
|
+
"gui.debugModal.commentYourCode.title": "Մեկնաբանություն թող սցենարի մասին",
|
|
20681
|
+
"gui.debugModal.commentYourCode.description1": "Կոդում մեկնաբանություններ ավելացնելը կարող է օգնել ուրիշներին հասկանալ քո կոդը։ Այն նաև կօգնի քեզ հիշել, թե ինչպես է աշխատում կոդդ, երբ հետագայում կրկին վերադառնաս դրան։",
|
|
20682
|
+
"gui.debugModal.commentYourCode.description2": "Աջ սեղմում արա սցենարների տարածքում և ընտրիր «Ավելացնել մեկնաբանություն»։ Առօրյա լեզվով բացատրիր, թե ինչ է անում որևէ բլոմասնիկկ կամ մասնիկների փոքր հաջորդականություն։",
|
|
20683
|
+
"gui.debugModal.takeABreak.title": "Ընդմիջում արա և մի փոքր հեռացիր",
|
|
20684
|
+
"gui.debugModal.takeABreak.description1": "Երբեմն որևէ խնդրի վրա չափազանց երկար կենտրոնանալը կարող է հակառակ ազդեցությունն ունենալ և հիասթափություն առաջացնել։",
|
|
20685
|
+
"gui.debugModal.takeABreak.description2": "Ընդմիջում արա և մի փոքր հեռացիր էկրանից՝ միտքդ թարմացնելու համար։ Որոշ ժամանակ հանգստանալուց, մեկ այլ բանով զբաղվելուց կամ մի փոքր ջուր խմելուց հետո կարող ես թարմ հայացքով վերադառնալ խնդրին։",
|
|
20686
|
+
"gui.debugModal.askForHelp.title": "Օգնություն խնդրիր",
|
|
20687
|
+
"gui.debugModal.askForHelp.description1": "Եթե դեռ չես կարողանում լուծել խնդիրը, կարող ես օգնություն խնդրել մեկ ուրիշից։ Փորձիր գտնել վրիպազերծման կամ օգնության ստուդիա և կիսվել քո նախագծով՝ օգնություն խնդրելով մեկնաբանության կամ նախագծի նշումների միջոցով։",
|
|
20688
|
+
"gui.debugModal.askForHelp.description2": "Խնդրիր մինչև երեք հոգու փորձարկել քո կոդը, քանի որ տարբեր մարդիկ կարող են տարբեր տեսանկյուններ կամ լուծումներ առաջարկել։",
|
|
20689
|
+
"gui.gui.shouldDeleteSprite": "Վստա՞հ ես, որ ցանկանում ես ջնջել այս կերպարը։",
|
|
20690
|
+
"gui.gui.shouldDeleteCostume": "Վստա՞հ ես, որ ցանկանում ես ջնջել այս զգեստը։",
|
|
20691
|
+
"gui.gui.shouldDeleteSound": "Վստա՞հ ես, որ ցանկանում ես ջնջել այս ձայնը։",
|
|
20692
|
+
"gui.gui.deleteAssetHeading": "Հաստատել նյութի ջնջումը",
|
|
20693
20693
|
"gui.SpriteInfo.direction": "Ուղղություն",
|
|
20694
20694
|
"gui.directionPicker.rotationStyles.allAround": "Շուրջ բոլորը",
|
|
20695
20695
|
"gui.directionPicker.rotationStyles.leftRight": "Ձախ/Աջ",
|
|
20696
20696
|
"gui.directionPicker.rotationStyles.dontRotate": "Չպտտել",
|
|
20697
20697
|
"gui.gui.addExtension": "Ավելացնել ընդլայնում",
|
|
20698
|
-
"gui.aria.menuBar": "
|
|
20698
|
+
"gui.aria.menuBar": "Վերին ընտրացանկի գոտի",
|
|
20699
20699
|
"gui.aria.editor": "Խմբագրիչ",
|
|
20700
|
-
"gui.aria.tabList": "
|
|
20701
|
-
"gui.aria.codePanel": "
|
|
20702
|
-
"gui.aria.costumesPanel": "
|
|
20703
|
-
"gui.aria.backdropsPanel": "
|
|
20704
|
-
"gui.aria.soundsPanel": "
|
|
20700
|
+
"gui.aria.tabList": "Ներդիրների ցանկ",
|
|
20701
|
+
"gui.aria.codePanel": "Այլագրի խմբագրման վահանակ",
|
|
20702
|
+
"gui.aria.costumesPanel": "Զգեստի խմբագրման վահանակ",
|
|
20703
|
+
"gui.aria.backdropsPanel": "Ետնապատկերի խմբագրման վահանակ",
|
|
20704
|
+
"gui.aria.soundsPanel": "Ձայնի խմբագրման վահանակ",
|
|
20705
20705
|
"gui.aria.backpack": "Ուսապարկ",
|
|
20706
|
-
"gui.aria.stageAndTarget": "
|
|
20706
|
+
"gui.aria.stageAndTarget": "Բեմ և թիրախ",
|
|
20707
20707
|
"gui.aria.stage": "Բեմ",
|
|
20708
|
-
"gui.aria.targetPane": "
|
|
20708
|
+
"gui.aria.targetPane": "Թիրախային վահանակի մատչելիության պիտակ",
|
|
20709
20709
|
"gui.gui.codeTab": "Սցենարներ",
|
|
20710
20710
|
"gui.gui.backdropsTab": "Ետնապատկեր",
|
|
20711
20711
|
"gui.gui.costumesTab": "Զգեստներ",
|
|
20712
20712
|
"gui.gui.soundsTab": "Ձայն",
|
|
20713
|
-
"gui.libraryItem.memberAssetImgAlt": "
|
|
20713
|
+
"gui.libraryItem.memberAssetImgAlt": "Կապույտ աստղի մանրապատկեր, որը ցույց է տալիս, որ տվյալ նյութը նախատեսված է անդամների համար",
|
|
20714
20714
|
"gui.extensionLibrary.comingSoon": "Շուտով",
|
|
20715
20715
|
"gui.extensionLibrary.requires": "Պահանջվում է",
|
|
20716
20716
|
"gui.extensionLibrary.collaboration": "Համագործակցությունը",
|
|
20717
20717
|
"gui.library.filterPlaceholder": "Որոնել",
|
|
20718
20718
|
"gui.library.allTag": "Բոլորը",
|
|
20719
20719
|
"gui.library.gettingStarted": "Սկսել",
|
|
20720
|
-
"gui.library.basics": "
|
|
20721
|
-
"gui.library.intermediate": "
|
|
20722
|
-
"gui.library.prompts": "
|
|
20723
|
-
"gui.library.membershipTag": "
|
|
20720
|
+
"gui.library.basics": "Հիմունքներ",
|
|
20721
|
+
"gui.library.intermediate": "Միջին մակարդակ",
|
|
20722
|
+
"gui.library.prompts": "Հուշող հրահանգներ",
|
|
20723
|
+
"gui.library.membershipTag": "Անդամակցություն",
|
|
20724
20724
|
"gui.loader.message1": "Մասնիկի ստեղծում ․․․ ",
|
|
20725
20725
|
"gui.loader.message2": "Կերպարների բեռնում ․․․ ",
|
|
20726
20726
|
"gui.loader.message3": "Ձայների բեռնում ․․․",
|
|
@@ -20737,7 +20737,7 @@ export default {
|
|
|
20737
20737
|
"gui.accountMenu.myClass": "Իմ դասը",
|
|
20738
20738
|
"gui.accountMenu.accountSettings": "Հաշվի կարգավորումներ",
|
|
20739
20739
|
"gui.accountMenu.signOut": "Դուրս գալ",
|
|
20740
|
-
"gui.authorInfo.byUser": "
|
|
20740
|
+
"gui.authorInfo.byUser": "<span>{name}</span> -ի կողմից",
|
|
20741
20741
|
"gui.menuBar.seeProjectPage": "Տեսնել նախագծի էջը",
|
|
20742
20742
|
"gui.menuBar.edit": "Խմբագրել",
|
|
20743
20743
|
"gui.menuBar.turboModeOff": "Անջատել արագացված ռեժիմը",
|
|
@@ -20754,9 +20754,9 @@ export default {
|
|
|
20754
20754
|
"login.needHelp": "Օգնության կարի՞ք ունեք:",
|
|
20755
20755
|
"form.validationRequired": "Այս դաշտը պարտադիր է",
|
|
20756
20756
|
"gui.menuBar.tutorialsLibrary": "Ձեռնարկներ",
|
|
20757
|
-
"gui.menuBar.debug": "
|
|
20758
|
-
"gui.menuBar.home": "
|
|
20759
|
-
"gui.menuBar.myStuff": "
|
|
20757
|
+
"gui.menuBar.debug": "Վրիպազերծում",
|
|
20758
|
+
"gui.menuBar.home": "Գլխավոր",
|
|
20759
|
+
"gui.menuBar.myStuff": "Իմ ունեցածը",
|
|
20760
20760
|
"gui.menuBar.restoreSprite": "Վերականգնել կերպարը",
|
|
20761
20761
|
"gui.menuBar.restoreSound": "Վերականգնել ձայնը",
|
|
20762
20762
|
"gui.menuBar.restoreCostume": "Վերականգնել զգեստը",
|
|
@@ -20828,8 +20828,8 @@ export default {
|
|
|
20828
20828
|
"gui.soundEditor.fadeIn": "Հզորացնել",
|
|
20829
20829
|
"gui.soundEditor.mute": "Անձայն",
|
|
20830
20830
|
"gui.SpriteInfo.spritePlaceholder": "Անուն",
|
|
20831
|
-
"gui.SpriteInfo.showSpriteAction": "
|
|
20832
|
-
"gui.SpriteInfo.hideSpriteAction": "
|
|
20831
|
+
"gui.SpriteInfo.showSpriteAction": "Ցուցադրել կերպարը",
|
|
20832
|
+
"gui.SpriteInfo.hideSpriteAction": "Թաքցնել կերպարը",
|
|
20833
20833
|
"gui.SpriteInfo.sprite": "Կերպար",
|
|
20834
20834
|
"gui.SpriteInfo.show": "Ցույց տալ",
|
|
20835
20835
|
"gui.SpriteInfo.size": "Չափս",
|
|
@@ -20844,9 +20844,9 @@ export default {
|
|
|
20844
20844
|
"gui.stageHeader.stageSizeSmall": "Անցնել փոքր բեմի",
|
|
20845
20845
|
"gui.stageHeader.stageSizeFull": "Ակտիվացնել լիաէկրան ռեժիմը",
|
|
20846
20846
|
"gui.stageHeader.stageSizeUnFull": "Դուրս գալ լիաէկրան ռեժիմից",
|
|
20847
|
-
"gui.stageHeader.saveThumbnail": "
|
|
20848
|
-
"gui.stageHeader.saveThumbnailMessage": "
|
|
20849
|
-
"gui.stageHeader.thumbnailTooltipTitle": "
|
|
20847
|
+
"gui.stageHeader.saveThumbnail": "Սահմանել մանրապատկերը",
|
|
20848
|
+
"gui.stageHeader.saveThumbnailMessage": "Վստա՞հ ես, որ ցանկանում ես սահմանել քո մանրապատկերը։",
|
|
20849
|
+
"gui.stageHeader.thumbnailTooltipTitle": "Ողջո՜ւյն։ 👋",
|
|
20850
20850
|
"gui.stageHeader.thumbnailTooltipBody": "<b>\"Set Thumbnail\"</b> has a new spot. It works by taking a snapshot of your stage and setting it as your project thumbnail.",
|
|
20851
20851
|
"gui.stageHeader.fullscreenControl": "Լիաէկրան կառավարում",
|
|
20852
20852
|
"gui.spriteSelector.addBackdropFromLibrary": "Ընտրել ետնապատկերը",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scratch-l10n",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.95",
|
|
4
4
|
"description": "Localization for the Scratch 3.0 components",
|
|
5
5
|
"main": "./dist/l10n.js",
|
|
6
6
|
"browser": "./src/index.mjs",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"lodash.defaultsdeep": "4.6.1",
|
|
56
56
|
"mkdirp": "3.0.1",
|
|
57
57
|
"transifex": "1.6.6",
|
|
58
|
-
"tsx": "4.23.
|
|
58
|
+
"tsx": "4.23.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@babel/cli": "7.29.7",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@types/lodash.defaultsdeep": "4.6.9",
|
|
74
74
|
"babel-loader": "8.4.1",
|
|
75
75
|
"babel-plugin-react-intl": "3.5.1",
|
|
76
|
-
"eslint": "9.39.
|
|
76
|
+
"eslint": "9.39.5",
|
|
77
77
|
"eslint-config-scratch": "11.0.53",
|
|
78
78
|
"format-message-cli": "6.2.4",
|
|
79
79
|
"globals": "17.7.0",
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"jsonlint": "1.6.3",
|
|
84
84
|
"p-limit": "2.3.0",
|
|
85
85
|
"p-queue": "3.2.0",
|
|
86
|
-
"prettier": "3.9.
|
|
86
|
+
"prettier": "3.9.5",
|
|
87
87
|
"rimraf": "2.7.1",
|
|
88
88
|
"scratch-semantic-release-config": "4.0.1",
|
|
89
|
-
"semantic-release": "25.0.
|
|
89
|
+
"semantic-release": "25.0.6",
|
|
90
90
|
"webpack": "4.47.0",
|
|
91
91
|
"webpack-cli": "3.3.12"
|
|
92
92
|
},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* Small shared helpers for working with caught values.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract a human-readable message from a caught value. `catch` values are typed `unknown` and are
|
|
8
|
+
* not guaranteed to be `Error` instances, so read `.message` only when it really is one.
|
|
9
|
+
* @param err - the caught value
|
|
10
|
+
* @returns the error message, or a string representation of a non-Error throw
|
|
11
|
+
*/
|
|
12
|
+
export const messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// interface to FreshDesk Solutions (knowledge base) api
|
|
2
|
+
import { messageOf } from './errors.mts'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Extend
|
|
@@ -12,7 +13,7 @@ export interface HttpError extends Error {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* Properties to provide when creating a Category
|
|
16
|
+
* Properties to provide when creating a Category through the Freshdesk API. Also works for updates.
|
|
16
17
|
* @see https://developers.freshdesk.com/api/#create_solution_category
|
|
17
18
|
*/
|
|
18
19
|
export interface FreshdeskCategoryCreate {
|
|
@@ -38,7 +39,7 @@ export interface FreshdeskCategory extends FreshdeskCategoryCreate {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
* Properties to provide when creating a Folder
|
|
42
|
+
* Properties to provide when creating a Folder through the Freshdesk API. Also works for updates.
|
|
42
43
|
* @see https://developers.freshdesk.com/api/#create_solution_folder
|
|
43
44
|
*/
|
|
44
45
|
export interface FreshdeskFolderCreate {
|
|
@@ -89,7 +90,7 @@ export enum FreshdeskFolderVisibility {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
|
-
* Properties to provide when creating an Article
|
|
93
|
+
* Properties to provide when creating an Article through the Freshdesk API. Also works for updates.
|
|
93
94
|
* @see https://developers.freshdesk.com/api/#create_solution_article
|
|
94
95
|
*/
|
|
95
96
|
export interface FreshdeskArticleCreate {
|
|
@@ -146,6 +147,23 @@ export interface FreshdeskSEOData {
|
|
|
146
147
|
meta_keywords?: string[]
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* A Freshdesk agent, as returned by the `agents/me` endpoint.
|
|
152
|
+
* @see https://developers.freshdesk.com/api/#me
|
|
153
|
+
*/
|
|
154
|
+
export interface FreshdeskAgent {
|
|
155
|
+
/** Unique ID of the agent */
|
|
156
|
+
id: number
|
|
157
|
+
/** Scope of tickets the agent can access */
|
|
158
|
+
ticket_scope?: number
|
|
159
|
+
/** The agent's contact details */
|
|
160
|
+
contact: {
|
|
161
|
+
name: string
|
|
162
|
+
email: string
|
|
163
|
+
active?: boolean
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
149
167
|
/**
|
|
150
168
|
* Wrapper for Freshdesk's REST API
|
|
151
169
|
*/
|
|
@@ -166,11 +184,14 @@ export class FreshdeskApi {
|
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
/**
|
|
169
|
-
* Checks the status of a response. If status is not ok, or the body is not JSON, raise exception
|
|
187
|
+
* Checks the status of a response. If status is not ok, or the body is not JSON, raise exception.
|
|
188
|
+
* On error, the response body is included in the message: Freshdesk reports the actual reason for
|
|
189
|
+
* a failure there (for example `{"code":"access_denied","message":"..."}`), so surfacing it turns
|
|
190
|
+
* an opaque "HTTP 403" into something diagnosable.
|
|
170
191
|
* @param res - The response object
|
|
171
192
|
* @returns the response if it is ok
|
|
172
193
|
*/
|
|
173
|
-
checkStatus(res: Response) {
|
|
194
|
+
async checkStatus(res: Response) {
|
|
174
195
|
if (res.ok) {
|
|
175
196
|
if (res.headers.get('content-type')?.includes('application/json')) {
|
|
176
197
|
return res
|
|
@@ -181,6 +202,12 @@ export class FreshdeskApi {
|
|
|
181
202
|
if (res.status === 403) {
|
|
182
203
|
message += ` -- this item may have been deleted in Freshdesk, or the API key lacks permission.`
|
|
183
204
|
}
|
|
205
|
+
// Read the body for the underlying reason. It is safe to consume here because we only reach this
|
|
206
|
+
// point on error and are about to throw; the caller never reads the body of a failed response.
|
|
207
|
+
const body = (await res.text().catch(() => '')).trim()
|
|
208
|
+
if (body) {
|
|
209
|
+
message += ` -- response: ${body.length > 500 ? `${body.slice(0, 500)}...` : body}`
|
|
210
|
+
}
|
|
184
211
|
const err = new Error(message) as HttpError
|
|
185
212
|
err.code = res.status
|
|
186
213
|
if (res.status === 429) {
|
|
@@ -191,7 +218,7 @@ export class FreshdeskApi {
|
|
|
191
218
|
|
|
192
219
|
async listCategories(): Promise<FreshdeskCategory[]> {
|
|
193
220
|
const res = await fetch(`${this.baseUrl}/api/v2/solutions/categories`, { headers: this.defaultHeaders })
|
|
194
|
-
this.checkStatus(res)
|
|
221
|
+
await this.checkStatus(res)
|
|
195
222
|
return (await res.json()) as FreshdeskCategory[]
|
|
196
223
|
}
|
|
197
224
|
|
|
@@ -199,7 +226,7 @@ export class FreshdeskApi {
|
|
|
199
226
|
const res = await fetch(`${this.baseUrl}/api/v2/solutions/categories/${category.id}/folders`, {
|
|
200
227
|
headers: this.defaultHeaders,
|
|
201
228
|
})
|
|
202
|
-
this.checkStatus(res)
|
|
229
|
+
await this.checkStatus(res)
|
|
203
230
|
return (await res.json()) as FreshdeskFolder[]
|
|
204
231
|
}
|
|
205
232
|
|
|
@@ -207,10 +234,22 @@ export class FreshdeskApi {
|
|
|
207
234
|
const res = await fetch(`${this.baseUrl}/api/v2/solutions/folders/${folder.id}/articles`, {
|
|
208
235
|
headers: this.defaultHeaders,
|
|
209
236
|
})
|
|
210
|
-
this.checkStatus(res)
|
|
237
|
+
await this.checkStatus(res)
|
|
211
238
|
return (await res.json()) as FreshdeskArticle[]
|
|
212
239
|
}
|
|
213
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Fetch the agent that the configured API key authenticates as. The account behind the token
|
|
243
|
+
* determines what the sync may read and write, so surfacing it makes a permission problem (such
|
|
244
|
+
* as a `403 access_denied` on a specific item) much easier to route to the right person.
|
|
245
|
+
* @returns the agent's id and contact details
|
|
246
|
+
*/
|
|
247
|
+
async getAuthenticatedAgent(): Promise<FreshdeskAgent> {
|
|
248
|
+
const res = await fetch(`${this.baseUrl}/api/v2/agents/me`, { headers: this.defaultHeaders })
|
|
249
|
+
await this.checkStatus(res)
|
|
250
|
+
return (await res.json()) as FreshdeskAgent
|
|
251
|
+
}
|
|
252
|
+
|
|
214
253
|
async updateCategoryTranslation(
|
|
215
254
|
id: FreshdeskCategory['id'],
|
|
216
255
|
locale: string,
|
|
@@ -226,7 +265,7 @@ export class FreshdeskApi {
|
|
|
226
265
|
body: JSON.stringify(body),
|
|
227
266
|
headers: this.defaultHeaders,
|
|
228
267
|
})
|
|
229
|
-
this.checkStatus(res)
|
|
268
|
+
await this.checkStatus(res)
|
|
230
269
|
return (await res.json()) as FreshdeskCategory
|
|
231
270
|
} catch (err) {
|
|
232
271
|
const httpError = err as HttpError
|
|
@@ -237,7 +276,7 @@ export class FreshdeskApi {
|
|
|
237
276
|
body: JSON.stringify(body),
|
|
238
277
|
headers: this.defaultHeaders,
|
|
239
278
|
})
|
|
240
|
-
this.checkStatus(res2)
|
|
279
|
+
await this.checkStatus(res2)
|
|
241
280
|
return (await res2.json()) as FreshdeskCategory
|
|
242
281
|
}
|
|
243
282
|
if (httpError.code === 429) {
|
|
@@ -263,7 +302,7 @@ export class FreshdeskApi {
|
|
|
263
302
|
body: JSON.stringify(body),
|
|
264
303
|
headers: this.defaultHeaders,
|
|
265
304
|
})
|
|
266
|
-
this.checkStatus(res)
|
|
305
|
+
await this.checkStatus(res)
|
|
267
306
|
return (await res.json()) as FreshdeskFolder
|
|
268
307
|
} catch (err) {
|
|
269
308
|
const httpError = err as HttpError
|
|
@@ -274,7 +313,7 @@ export class FreshdeskApi {
|
|
|
274
313
|
body: JSON.stringify(body),
|
|
275
314
|
headers: this.defaultHeaders,
|
|
276
315
|
})
|
|
277
|
-
this.checkStatus(res2)
|
|
316
|
+
await this.checkStatus(res2)
|
|
278
317
|
return (await res2.json()) as FreshdeskFolder
|
|
279
318
|
}
|
|
280
319
|
if (httpError.code === 429) {
|
|
@@ -300,7 +339,7 @@ export class FreshdeskApi {
|
|
|
300
339
|
body: JSON.stringify(body),
|
|
301
340
|
headers: this.defaultHeaders,
|
|
302
341
|
})
|
|
303
|
-
this.checkStatus(res)
|
|
342
|
+
await this.checkStatus(res)
|
|
304
343
|
return (await res.json()) as FreshdeskArticle
|
|
305
344
|
} catch (err) {
|
|
306
345
|
const httpError = err as HttpError
|
|
@@ -311,7 +350,7 @@ export class FreshdeskApi {
|
|
|
311
350
|
body: JSON.stringify(body),
|
|
312
351
|
headers: this.defaultHeaders,
|
|
313
352
|
})
|
|
314
|
-
this.checkStatus(res2)
|
|
353
|
+
await this.checkStatus(res2)
|
|
315
354
|
return (await res2.json()) as FreshdeskArticle
|
|
316
355
|
}
|
|
317
356
|
if (httpError.code === 429) {
|
|
@@ -323,4 +362,21 @@ export class FreshdeskApi {
|
|
|
323
362
|
}
|
|
324
363
|
}
|
|
325
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Log which agent the given Freshdesk client authenticates as. Never throws: identifying the agent
|
|
367
|
+
* is a diagnostic aid, so a failure here (for example an invalid token) is reported and swallowed
|
|
368
|
+
* rather than aborting the caller, which will hit the same problem with a clearer error of its own.
|
|
369
|
+
* The agent's email is intentionally omitted to keep it out of public CI logs; the name and id are
|
|
370
|
+
* enough to find the account in Freshdesk.
|
|
371
|
+
* @param fd - a configured Freshdesk client
|
|
372
|
+
*/
|
|
373
|
+
export const logAuthenticatedAgent = async (fd: FreshdeskApi): Promise<void> => {
|
|
374
|
+
try {
|
|
375
|
+
const agent = await fd.getAuthenticatedAgent()
|
|
376
|
+
console.log(`Freshdesk token authenticated as "${agent.contact.name}" (agent id ${agent.id}).`)
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.warn(`Could not identify the Freshdesk agent for the configured token: ${messageOf(error)}`)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
326
382
|
export default FreshdeskApi
|