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.
@@ -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": "Tutorials icon",
20569
- "gui.cards.shrinkIcon": "Shrink icon",
20570
- "gui.cards.expandIcon": "Expand icon",
20571
- "gui.cards.closeIcon": "Close icon",
20572
- "gui.cards.leftArrowIcon": "Left arrow icon",
20573
- "gui.cards.rightArrowIcon": "Right arrow icon",
20574
- "gui.cards.previousStepButton": "Previous step",
20575
- "gui.cards.nextStepButton": "Next step",
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": "yes",
20586
- "gui.confirmationPrompt.cancel": "no",
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": "Previous",
20637
- "gui.debugModal.next": "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": "Perhaps you aren't using a loop when you should be? For instance, if you are using a conditional statement block like \"if then,\" does the program only need to check if it is true or false once? Or does it need to check continuously, in which case, you would want to place your conditional statement inside a forever loop?",
20664
- "gui.debugModal.timingAndParallelism.title": "Think About Timing & Parallelism",
20665
- "gui.debugModal.timingAndParallelism.sectionTitle": "Timing & Parallelism",
20666
- "gui.debugModal.timingAndParallelism.description1": "Do you have multiple events trying to run at the same time? If two sequences are programmed to start at the same time, you can get unpredictable behavior.",
20667
- "gui.debugModal.timingAndParallelism.description2": "Add small waits, broadcasts, or user interaction (like clicking or pressing a key) to see if this affects the result.",
20668
- "gui.debugModal.thinkAboutBlockOptions.title": "Think About Block Options",
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": "Some blocks look similar but can behave differently, such as “set” vs “change” or “play until done” vs “start.”",
20671
- "gui.debugModal.thinkAboutBlockOptions.description3": "Try using a similar block in place of what you have, and see if this affects the result.",
20672
- "gui.debugModal.checkTheValues.title": "Check the Values",
20673
- "gui.debugModal.checkTheValues.description1": "If you are using variables or reporter blocks, check the value at the moment the code sequence is run.",
20674
- "gui.debugModal.checkTheValues.description2": "Do/should all the sprites control a variable, or should only one sprite have control?",
20675
- "gui.debugModal.checkTheValues.description3": "Where is the value reset? Where is it changed?",
20676
- "gui.debugModal.checkCodeSequence.title": "Check Code Sequence",
20677
- "gui.debugModal.checkCodeSequence.description1": "Check that your code sequence is attached to the correct sprite or the backdrop, if appropriate.",
20678
- "gui.debugModal.checkCodeSequence.description2": "If you need to move your code to another sprite, click and drag it until you are hovering over the correct sprite. Release it once the sprite wiggles.",
20679
- "gui.debugModal.checkCodeSequence.description3": "You can also use your Backpack (bottom of screen) to store and move your code or assets.",
20680
- "gui.debugModal.commentYourCode.title": "Comment Your Code",
20681
- "gui.debugModal.commentYourCode.description1": "Adding comments to your code can help others looking at your code to understand it. It can also help you remember how your code works when you come back to it later.",
20682
- "gui.debugModal.commentYourCode.description2": "Right click on script area to “Add Comment.” Use everyday language to explain what a block, or small sequence of blocks, does.",
20683
- "gui.debugModal.takeABreak.title": "Take a Break, Step Away",
20684
- "gui.debugModal.takeABreak.description1": "Sometimes, spending too much time focused on an issue can be counterproductive and frustrating.",
20685
- "gui.debugModal.takeABreak.description2": "Take a break and step away from the screen to clear your mind. After some rest, focusing on something else, or getting some water, you can approach the problem with fresh eyes.",
20686
- "gui.debugModal.askForHelp.title": "Ask for Help",
20687
- "gui.debugModal.askForHelp.description1": "If you are still stuck, you can ask for help from a peer. Try finding a debugging/help studio and share your project, asking for help in a comment or the project notes.",
20688
- "gui.debugModal.askForHelp.description2": "Ask one to three people to try your code, as different people may have different perspectives or solutions!",
20689
- "gui.gui.shouldDeleteSprite": "Are you sure you want to delete this sprite?",
20690
- "gui.gui.shouldDeleteCostume": "Are you sure you want to delete this costume?",
20691
- "gui.gui.shouldDeleteSound": "Are you sure you want to delete this sound?",
20692
- "gui.gui.deleteAssetHeading": "Confirm Asset Deletion",
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": "Menu topbar",
20698
+ "gui.aria.menuBar": "Վերին ընտրացանկի գոտի",
20699
20699
  "gui.aria.editor": "Խմբագրիչ",
20700
- "gui.aria.tabList": "Tab list",
20701
- "gui.aria.codePanel": "Code editor panel",
20702
- "gui.aria.costumesPanel": "Costumes editor panel",
20703
- "gui.aria.backdropsPanel": "Backdrops editor panel",
20704
- "gui.aria.soundsPanel": "Sounds editor panel",
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": "Stage and target",
20706
+ "gui.aria.stageAndTarget": "Բեմ և թիրախ",
20707
20707
  "gui.aria.stage": "Բեմ",
20708
- "gui.aria.targetPane": "Target pane",
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": "Blue star icon indicating an asset is for members",
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": "Basics",
20721
- "gui.library.intermediate": "Intermediate",
20722
- "gui.library.prompts": "Prompts",
20723
- "gui.library.membershipTag": "Membership",
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": "by <span>{name}</span>",
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": "Debug",
20758
- "gui.menuBar.home": "Home",
20759
- "gui.menuBar.myStuff": "My Stuff",
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": "Show sprite",
20832
- "gui.SpriteInfo.hideSpriteAction": "Hide sprite",
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": "Set Thumbnail",
20848
- "gui.stageHeader.saveThumbnailMessage": "Are you sure you want to set your thumbnail?",
20849
- "gui.stageHeader.thumbnailTooltipTitle": "Hey there! 👋",
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.93",
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.0"
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.4",
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.4",
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.5",
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 throught the Freshdesk API. Also works for updates.
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 throught the Freshdesk API. Also works for updates.
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 throught the Freshdesk API. Also works for updates.
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