diff --git a/.github/SPONSORS.json b/.github/SPONSORS.json
new file mode 100644
index 0000000..1e9399a
--- /dev/null
+++ b/.github/SPONSORS.json
@@ -0,0 +1,90 @@
+{
+ "_INFO":"This document contains all Open Ticket sponsors.",
+ "SPONSOR_TEMPLATE":{
+ "name":"INSERT_NAME",
+ "pictureUrl":"INSERT_PICTURE_URL",
+ "profileUrl":"INSERT_PROFILE_URL",
+ "sectionId":"INSERT_ID"
+ },
+ "SECTION_TEMPLATE":{
+ "name":"INSERT_NAME",
+ "id":"INSERT_ID",
+ "pfpSize":50,
+ "pfpColumns":5,
+ "withNames":true
+ },
+ "sections":[
+ {
+ "name":"๐ Platinum Sponsors",
+ "id":"platinum",
+ "pfpSize":120,
+ "pfpColumns":4,
+ "withNames":true
+ },
+ {
+ "name":"๐ฅ Gold Sponsors",
+ "id":"gold",
+ "pfpSize":60,
+ "pfpColumns":8,
+ "withNames":true
+ },
+ {
+ "name":"๐ฅ Silver Sponsors",
+ "id":"silver",
+ "pfpSize":40,
+ "pfpColumns":16,
+ "withNames":false
+ },
+ {
+ "name":"โค๏ธ Past Sponsors",
+ "id":"past",
+ "pfpSize":30,
+ "pfpColumns":20,
+ "withNames":false
+ }
+ ],
+ "sponsors":[
+ {
+ "name":"Guillee3",
+ "pictureUrl":"https://github.com/guillee3.png",
+ "profileUrl":"https://github.com/guillee3",
+ "sectionId":"platinum"
+ },
+ {
+ "name":"Jacob Humston",
+ "pictureUrl":"https://github.com/jacobhumston.png",
+ "profileUrl":"https://github.com/jacobhumston",
+ "sectionId":"platinum"
+ },
+ {
+ "name":"SpyEye2",
+ "pictureUrl":"https://github.com/SpyEye2.png",
+ "profileUrl":"https://github.com/SpyEye2",
+ "sectionId":"gold"
+ },
+ {
+ "name":"DOSEV5",
+ "pictureUrl":"https://github.com/DOSEV5.png",
+ "profileUrl":"https://github.com/DOSEV5",
+ "sectionId":"gold"
+ },
+ {
+ "name":"YeeetSK",
+ "pictureUrl":"https://github.com/YeeetSK.png",
+ "profileUrl":"https://github.com/YeeetSK",
+ "sectionId":"silver"
+ },
+ {
+ "name":"BENZORICH",
+ "pictureUrl":"https://github.com/BENZORICH.png",
+ "profileUrl":"https://github.com/BENZORICH",
+ "sectionId":"silver"
+ },
+ {
+ "name":"Mods HD",
+ "pictureUrl":"https://github.com/mods-hd.png",
+ "profileUrl":"https://github.com/mods-hd",
+ "sectionId":"silver"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.github/SPONSORS.svg b/.github/SPONSORS.svg
new file mode 100644
index 0000000..e3edf79
--- /dev/null
+++ b/.github/SPONSORS.svg
@@ -0,0 +1,61 @@
+
\ No newline at end of file
diff --git a/.tools/pterodactyl-eggs/README.md b/.github/pterodactyl-eggs/README.md
similarity index 100%
rename from .tools/pterodactyl-eggs/README.md
rename to .github/pterodactyl-eggs/README.md
diff --git a/.tools/pterodactyl-eggs/openticket-egg-dev.json b/.github/pterodactyl-eggs/openticket-egg-dev.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-dev.json
rename to .github/pterodactyl-eggs/openticket-egg-dev.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-main.json b/.github/pterodactyl-eggs/openticket-egg-main.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-main.json
rename to .github/pterodactyl-eggs/openticket-egg-main.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v3.5.9.json b/.github/pterodactyl-eggs/openticket-egg-v3.5.9.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v3.5.9.json
rename to .github/pterodactyl-eggs/openticket-egg-v3.5.9.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.0.7.json b/.github/pterodactyl-eggs/openticket-egg-v4.0.7.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.0.7.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.0.7.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.1.0.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.0.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.1.0.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.1.0.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.1.1.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.1.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.1.1.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.1.1.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.1.2.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.2.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.1.2.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.1.2.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.1.3.json b/.github/pterodactyl-eggs/openticket-egg-v4.1.3.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.1.3.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.1.3.json
diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.2.0.json b/.github/pterodactyl-eggs/openticket-egg-v4.2.0.json
similarity index 100%
rename from .tools/pterodactyl-eggs/openticket-egg-v4.2.0.json
rename to .github/pterodactyl-eggs/openticket-egg-v4.2.0.json
diff --git a/.gitignore b/.gitignore
index 193d71e..6363a68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,9 +19,7 @@ otdebug.txt
.backup/*
.tools/*
-!.tools/createDocs.js
-!.tools/mergeTranslations.js
-!.tools/typedoc-config.json
-!.tools/pterodactyl-eggs/
+!.tools/createSponsors.ts
+!.tools/mergeTranslations.ts
!.tools/docker-compose.yml
!.tools/dockerfile
\ No newline at end of file
diff --git a/.tools/createDocs.js b/.tools/createDocs.js
deleted file mode 100644
index efb5a05..0000000
--- a/.tools/createDocs.js
+++ /dev/null
@@ -1,328 +0,0 @@
-//@ts-check
-const fs = require("fs")
-
-/*////// OT DOCS GENERATOR ////////
-What does this file do?:
-Well, this file will parse the result returned from typedoc (the documentation generator).
-The resulting file will then be used in the OT Docs markdown generator to generate the Open Ticket API Reference.
-
-In short:
-This file generates the documentation for Open Ticket :)
-
-Contributing:
-If you want to contribute to the Open Ticket Docs, please run the following command:
-
-> npm run docs
-
-This will give you a "result.json" file which can then be used in the OT Docs.
-Further instructions can be found there.
-/////////////////////////////////*/
-
-if (!fs.existsSync(".docs/typedoc-result.json")){
- console.log("Unable to generate documentation! Run typedoc first!")
- process.exit(1)
-}
-const result = JSON.parse(fs.readFileSync(".docs/typedoc-result.json").toString())
-const availableElements = []
-const skipElementNames = [
- "#opendiscord-types"
-]
-
-const handleFunction = (memberType) => {
- if (!memberType || !memberType.signatures || !Array.isArray(memberType.signatures)) return {type:"unknown"}
-
- //try to get signature
- let signature = memberType.signatures.find((s) => !s.typeParameters)
- if (!signature) signature = memberType.signatures[0]
- if (!signature) return {type:"unknown"}
-
- //try to get comment
- let comment = signature.comment
- if (!comment) comment = memberType.signatures.find((s) => s.comment)?.comment
- if (!comment) comment = null
-
- const signatureInherited = signature.flags.isInherited ? true : false
- const signatureComment = comment ? comment.summary?.map((c) => c.text).join("") : null
- const signatureParameters = signature.parameters?.map((p) => {
- return {name:p.name,details:handleType(p.type)}
- }) ?? []
- const signatureReturns = handleType(signature.type)
-
- return {type:"function",inherited:signatureInherited,comment:signatureComment,parameters:signatureParameters,returns:signatureReturns}
-}
-
-const handleObject = (memberType) => {
- if (!memberType || !memberType.children || !Array.isArray(memberType.children)) return {type:"unknown"}
- const propertyIds = memberType.groups?.find((g) => g.title == "Properties")?.children ?? []
- const methodIds = memberType.groups?.find((g) => g.title == "Methods")?.children ?? []
-
- const objectChildren = []
- for (const member of (memberType.children ?? [])){
- const memberName = member.name
- const memberType = propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other"
- let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null
-
- const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null
- let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null
-
- let memberDetails = null
- if (memberType == "property"){
- //INTERFACE => PROPERTY
- memberDetails = handleType(member.type)
- }else if (memberType == "method"){
- //INTERFACE => METHOD
- memberDetails = handleFunction(member)
- memberComment = memberDetails.comment
- }
-
- objectChildren.push({
- type:memberType,
- name:memberName,
- comment:memberComment,
- source:memberSource,
- details:memberDetails
- })
- }
- return {type:"object",children:objectChildren}
-}
-
-const handleType = (memberType) => {
- if (!memberType || !memberType.type) return {type:"unknown"}
- else if (memberType.type == "intrinsic") return {type:"primitive",name:memberType.name}
- else if (memberType.type == "array") return {type:"array",child:handleType(memberType.elementType)}
- else if (memberType.type == "union") return {type:"union",children:memberType.types.map((t) => handleType(t))}
- else if (memberType.type == "intersection") return {type:"intersection",children:memberType.types.map((t) => handleType(t))}
- else if (memberType.type == "reference"){
- const referenceTypeArguments = (memberType.typeArguments) ? memberType.typeArguments.map((ta) => handleType(ta)) : null
- let referenceType = availableElements.find((el) => el.name == memberType.name)?.type ?? null
-
- if (referenceType){
- //member is known type (from Open Ticket)
- return {type:"reference",name:memberType.name,target:referenceType,typeArguments:referenceTypeArguments}
- }else if (memberType.package){
- //member is type from node.js or another package
- if (memberType.package == "typescript") return {type:"internal",name:memberType.name,typeArguments:referenceTypeArguments}
- if (memberType.package == "open-ticket" && memberType.refersToTypeParameter) return {type:"typeParam",name:memberType.name,typeArguments:referenceTypeArguments}
- else return {type:"external",package:memberType.package,name:memberType.name,typeArguments:referenceTypeArguments}
- }else return {type:"unknown"}
- }
- else if (memberType.type == "literal") return {type:"literal",name:(typeof memberType.value == "string") ? JSON.stringify(memberType.value) : String(memberType.value)}
- else if (memberType.type == "typeOperator"){
- if (memberType.operator == "keyof") return {type:"keyof",child:handleType(memberType.target)}
- else if (memberType.operator == "readonly") return {type:"readonly",child:handleType(memberType.target)}
- else if (memberType.operator == "unique") return {type:"unique",child:handleType(memberType.target)}
- }
- else if (memberType.type == "conditional") return {type:"conditional",checker:handleType(memberType.checkType),extends:handleType(memberType.extendsType),trueValue:handleType(memberType.trueType),falseValue:handleType(memberType.falseType)}
- else if (memberType.type == "indexedAccess") return {type:"index",index:handleType(memberType.indexType),object:handleType(memberType.objectType)}
- else if (memberType.type == "mapped") return {type:"mapped",parameterName:memberType.parameter,parameter:handleType(memberType.parameterType),template:handleType(memberType.templateType)}
- else if (memberType.type == "optional") return {type:"optional",child:handleType(memberType.elementType)}
- else if (memberType.type == "predicate") return {type:"predicate",name:memberType.name,target:handleType(memberType.targetType)}
- else if (memberType.type == "query") return {type:"query",target:handleType(memberType.queryType)}
- else if (memberType.type == "rest") return {type:"rest",child:handleType(memberType.elementType)}
- else if (memberType.type == "tuple") return {type:"tuple",children:memberType.elements.map((t) => handleType(t))}
- else if (memberType.type == "templateLiteral") return {type:"template",head:memberType.head,tails:memberType.tail.map((t) => {
- return {element:handleType(t[0]),text:t[1]}
- })}
- else if (memberType.type == "reflection" && memberType.declaration && memberType.declaration.signatures && memberType.declaration.signatures[0]){
- return handleFunction(memberType.declaration)
- }
- else if (memberType.type == "reflection"){
- return handleObject(memberType.declaration)
- }
- else return {type:"unknown"}
-}
-
-for (const file of result.children){
- const classIds = file.groups?.find((g) => g.title == "Classes")?.children ?? []
- const interfaceIds = file.groups?.find((g) => g.title == "Interfaces")?.children ?? []
- const typeIds = file.groups?.find((g) => g.title == "Type Aliases")?.children ?? []
- const enumIds = file.groups?.find((g) => g.title == "Enumerations")?.children ?? []
- const varIds = file.groups?.find((g) => g.title == "Variables")?.children ?? []
- const funcIds = file.groups?.find((g) => g.title == "Functions")?.children ?? []
-
- for (const declaration of file.children){
- const declarationName = declaration.name
- const declarationType = classIds.includes(declaration.id) ? "class" : interfaceIds.includes(declaration.id) ? "interface" : typeIds.includes(declaration.id) ? "type" : enumIds.includes(declaration.id) ? "enum" : varIds.includes(declaration.id) ? "variable" : funcIds.includes(declaration.id) ? "function" : "other"
- availableElements.push({name:declarationName,type:declarationType})
- }
-}
-
-const exported = []
-for (const file of result.children){
- const classIds = file.groups?.find((g) => g.title == "Classes")?.children ?? []
- const interfaceIds = file.groups?.find((g) => g.title == "Interfaces")?.children ?? []
- const typeIds = file.groups?.find((g) => g.title == "Type Aliases")?.children ?? []
- const enumIds = file.groups?.find((g) => g.title == "Enumerations")?.children ?? []
- const varIds = file.groups?.find((g) => g.title == "Variables")?.children ?? []
- const funcIds = file.groups?.find((g) => g.title == "Functions")?.children ?? []
-
- for (const declaration of (file.children ?? [])){
- const declarationName = declaration.name
- if (skipElementNames.includes(declarationName)) continue
-
- const declarationType = classIds.includes(declaration.id) ? "class" : interfaceIds.includes(declaration.id) ? "interface" : typeIds.includes(declaration.id) ? "type" : enumIds.includes(declaration.id) ? "enum" : varIds.includes(declaration.id) ? "variable" : funcIds.includes(declaration.id) ? "function" : "other"
- const declarationTypeParams = (declaration.typeParameters) ? declaration.typeParameters.map((tp) => {
- return {name:tp.name,type:handleType(tp.type)}
- }) : null
- const declarationComment = declaration.comment?.summary?.map((c) => c.text).join("") ?? null
- const declarationConstant = declaration.flags.isConst ? true : false
-
- const rawDeclarationSource = declaration.sources ? (declaration.sources[0] ?? null) : null
- const declarationSource = rawDeclarationSource ? rawDeclarationSource.fileName+":"+rawDeclarationSource.line+":"+rawDeclarationSource.character : null
-
- const declarationChildren = []
- if (declarationType == "class"){
- //CLASS
- const constructorIds = declaration.groups?.find((g) => g.title == "Constructors")?.children ?? []
- const propertyIds = declaration.groups?.find((g) => g.title == "Properties")?.children ?? []
- const methodIds = declaration.groups?.find((g) => g.title == "Methods")?.children ?? []
-
- for (const member of (declaration.children ?? [])){
- const memberName = member.name
- const memberType = constructorIds.includes(member.id) ? "constructor" : propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other"
- let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null
-
- const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null
- let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null
-
- const memberInherited = member.flags.isInherited ? true : false
- const memberStatic = member.flags.isStatic ? true : false
- const memberProtected = member.flags.isProtected ? true : false
- const memberOptional = member.flags.isOptional ? true : false
- const memberReadonly = member.flags.isReadonly ? true : false
-
- let memberDetails = null
- if (memberType == "property"){
- //CLASS => PROPERTY
- memberDetails = handleType(member.type)
-
- }else if (memberType == "method"){
- //CLASS => METHOD
- memberDetails = handleFunction(member)
- memberComment = memberDetails.comment
-
- }else if (memberType == "constructor"){
- //CLASS => CONSTRUCTOR
- memberDetails = handleFunction(member)
- memberComment = memberDetails.comment
- }
-
- declarationChildren.push({
- type:memberType,
- name:memberName,
- comment:memberComment,
- source:memberSource,
- details:memberDetails,
- inherited:memberInherited,
- static:memberStatic,
- protected:memberProtected,
- optional:memberOptional,
- readonly:memberReadonly
- })
- }
- }else if (declarationType == "interface"){
- //INTERFACE
- const propertyIds = declaration.groups?.find((g) => g.title == "Properties")?.children ?? []
- const methodIds = declaration.groups?.find((g) => g.title == "Methods")?.children ?? []
-
- for (const member of (declaration.children ?? [])){
- const memberName = member.name
- const memberType = propertyIds.includes(member.id) ? "property" : methodIds.includes(member.id) ? "method" : "other"
- let memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null
-
- const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null
- let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null
-
- const memberInherited = member.flags.isInherited ? true : false
- const memberStatic = member.flags.isStatic ? true : false
- const memberProtected = member.flags.isProtected ? true : false
- const memberOptional = member.flags.isOptional ? true : false
- const memberReadonly = member.flags.isReadonly ? true : false
-
- let memberDetails = null
- if (memberType == "property"){
- //INTERFACE => PROPERTY
- memberDetails = handleType(member.type)
- }else if (memberType == "method"){
- //INTERFACE => METHOD
- memberDetails = handleFunction(member)
- memberComment = memberDetails.comment
- }
-
- declarationChildren.push({
- type:memberType,
- name:memberName,
- comment:memberComment,
- source:memberSource,
- details:memberDetails,
- inherited:memberInherited,
- static:memberStatic,
- protected:memberProtected,
- optional:memberOptional,
- readonly:memberReadonly
- })
- }
- }else if (declarationType == "type"){
- //TYPE
- declarationChildren.push(handleType(declaration.type))
- }else if (declarationType == "enum"){
- //ENUM
- const enumerableIds = declaration.groups?.find((g) => g.title == "Enumeration Members")?.children ?? []
-
- for (const member of (declaration.children ?? [])){
- const memberName = member.name
- const memberType = enumerableIds.includes(member.id) ? "enumerable" : "other"
- const memberComment = member.comment?.summary?.map((c) => c.text).join("") ?? null
-
- const rawMemberSource = member.sources ? (member.sources[0] ?? null) : null
- let memberSource = rawMemberSource ? rawMemberSource.fileName+":"+rawMemberSource.line+":"+rawMemberSource.character : null
-
- let memberDetails = null
- if (memberType == "enumerable"){
- //ENUM => ENUMERABLE
- memberDetails = handleType(member.type)
- }
-
- const memberInherited = member.flags.isInherited ? true : false
- const memberStatic = member.flags.isStatic ? true : false
- const memberProtected = member.flags.isProtected ? true : false
- const memberOptional = member.flags.isOptional ? true : false
- const memberReadonly = member.flags.isReadonly ? true : false
-
- declarationChildren.push({
- type:memberType,
- name:memberName,
- comment:memberComment,
- source:memberSource,
- details:memberDetails,
- inherited:memberInherited,
- static:memberStatic,
- protected:memberProtected,
- optional:memberOptional,
- readonly:memberReadonly
- })
- }
- }else if (declarationType == "variable"){
- //VARIABLE
- declarationChildren.push(handleType(declaration.type))
-
- }else if (declarationType == "function"){
- //FUNCTION
- declarationChildren.push(handleFunction(declaration))
-
- }
-
- exported.push({
- type:declarationType,
- name:declarationName,
- comment:declarationComment,
- constant:declarationConstant,
- source:declarationSource,
- children:declarationChildren,
- typeParams:declarationTypeParams
- })
- }
-}
-
-fs.writeFileSync(".docs/result.json",JSON.stringify(exported,null,"\t"))
-fs.rmSync(".docs/typedoc-result.json")
\ No newline at end of file
diff --git a/.tools/createSponsors.ts b/.tools/createSponsors.ts
new file mode 100644
index 0000000..6b2d496
--- /dev/null
+++ b/.tools/createSponsors.ts
@@ -0,0 +1,131 @@
+import fs from "fs"
+import path from "path"
+import crypto from "crypto"
+
+const sponsorData: {sponsors:Sponsor[],sections:Section[]} = JSON.parse(fs.readFileSync(path.join(process.cwd(),"./.github/SPONSORS.json")).toString())
+
+//CONSTANTS
+const CORNER_RADIUS = 10
+const SPACE_MULTIPLIER = 1.2
+const SVG_WIDTH = 1000
+
+//TYPES
+interface Sponsor {
+ name:string,
+ pictureUrl:string,
+ profileUrl:string,
+ sectionId:string
+}
+interface Section {
+ name:string,
+ id:string,
+ pfpSize:number,
+ pfpColumns:number,
+ withNames:boolean
+}
+
+
+//FUNCTIONS
+async function downloadPfpToBase64URL(url:string){
+ const res = await fetch(url,{method:"GET"})
+ if (!res.ok) return null
+ const buffer = Buffer.from(await res.arrayBuffer())
+ console.log("Downloaded picture URL:",url)
+ return "data:image/png;base64,"+buffer.toString("base64")
+}
+
+function createTitle(yPos:number,name:string){
+ return `
-
-
+
-Open Ticket is the most advanced and customizable Discord ticket bot available. With 350+ configurable settings, you have full control over every aspect of your ticket system!
-From HTML transcripts and Advanced Plugins to Claiming & Pinning, Questions & Modals, Detailed Statistics, and much more.
-The bot is fully translated into 37+ languages and has been battle-tested in large Discord servers.
+Open Ticket is the most advanced and customizable Discord ticket bot available right now. With 350+ configurable settings, you have full control over almost every aspect of your ticket system!
+From HTML transcripts and Advanced Plugins to Claiming & Pinning, Modal Questions & Limits, Detailed Statistics, and much more.
+The bot is fully translated into 38+ languages and has been battle-tested in large Discord servers.
Need help or want to get involved? Feel free to join our Discord server.
โค๏ธ Love Open Ticket? Sponsorships help fuel our HTML transcript servers and future features! โค๏ธ
+โค๏ธ Love Open Ticket? Sponsorships help fuel our HTML transcript servers and future features! โค๏ธ
+
+
![]() |
-![]() |
-![]() |
-
| guillee3 | -yeeetSK | -jacobhumston | -
-
-
-
-
-
## ๐ธ Preview
@@ -134,14 +140,14 @@ A list of amazing people who have contributed or provided supported for **Open T
### ๐ฌ Translators
-With the amazing support of our translators, we've been able to translate Open Ticket in more than **37 languages**!
+With the amazing support of our translators, we've been able to translate Open Ticket in more than **38 languages**!
#### Categories:
- **๐ข Available**
- **๐ค Partially Made Using AI**
- **๐ Incomplete**
- **๐ด Unavailable/Outdated**
-|๐ |Languages (37) |Maintainer (Github/Discord) |
+|๐ |Languages (38) |Maintainer (Github/Discord) |
|----|---------------------|--------------------------------|
|๐ข |๐ฌ๐ง English |djj123dj |
|๐ข |๐ณ๐ฑ Dutch |djj123dj |
@@ -168,6 +174,7 @@ With the amazing support of our translators, we've been able to translate Open T
|๐ข |๐ง๐ฉ Bengali |HanumeshGupta |
|๐ข |โ Catalan |guillee3 |
|๐ข |๐จ๐ณ Traditional Chinese|me.october |
+|๐ข |๐ฐ๐ญ Khmer (Cambodia) |yuuslokrobjakkroval |
|๐ค |๐ช๐ช Estonian |iamnotmega |
|๐ค |๐ซ๐ฎ Finnish |iamnotmega |
|๐ค |๐ฏ๐ต Japanese |HanumeshGupta |
diff --git a/package.json b/package.json
index 92ca691..44d4d3f 100644
--- a/package.json
+++ b/package.json
@@ -19,8 +19,8 @@
"test": "node index.js --dev-config --dev-database --soft-plugins",
"testsetup": "node index.js --cli --dev-config --dev-database --soft-plugins",
"testnc": "node index.js --no-compile --dev-config --dev-database --soft-plugins",
- "docs": "npx typedoc --options .tools/typedoc-config.json && node .tools/createDocs.js",
- "mergelang": "node .tools/mergeTranslations.js"
+ "tools:mergelang": "bun run .tools/mergeTranslations.js",
+ "tools:sponsors": "bun run .tools/createSponsors.ts"
},
"type": "module",
"license": "GPL-3.0-only",