diff --git a/tools/scripts/createReleaseChangelog.ts b/tools/scripts/createReleaseChangelog.ts index df2a5936..cff9ab2c 100644 --- a/tools/scripts/createReleaseChangelog.ts +++ b/tools/scripts/createReleaseChangelog.ts @@ -10,7 +10,8 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import dayjs from 'dayjs'; import { createCommitChangelog } from './release/CommitChangelog.utils.ts'; -import { createTranslationChangelog, validateWeblateDates } from './release/TranslationChangelog.utils.ts'; +import { createTranslationChangelog } from './release/TranslationChangelog.utils.ts'; +import { validateWeblateDates } from './weblate/Weblate.utils.ts'; const { sha, date } = yargs(hideBin(process.argv)) .options({ diff --git a/tools/scripts/createTranslationChangelog.ts b/tools/scripts/createTranslationChangelog.ts index cca05075..6be67e55 100644 --- a/tools/scripts/createTranslationChangelog.ts +++ b/tools/scripts/createTranslationChangelog.ts @@ -8,10 +8,8 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { - createTranslationChangelog, - TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT, -} from './release/TranslationChangelog.utils.ts'; +import { createTranslationChangelog } from './release/TranslationChangelog.utils.ts'; +import { TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT } from './weblate/Weblate.constants.ts'; const { afterDate, beforeDate, requiredContributionCount, keepKnownContributors } = yargs(hideBin(process.argv)) .options({ diff --git a/tools/scripts/release/TranslationChangelog.utils.ts b/tools/scripts/release/TranslationChangelog.utils.ts index 552e38f9..46086346 100644 --- a/tools/scripts/release/TranslationChangelog.utils.ts +++ b/tools/scripts/release/TranslationChangelog.utils.ts @@ -11,136 +11,16 @@ import fs from 'fs'; import readline from 'readline'; import { ControlledPromise } from '@/lib/ControlledPromise.ts'; import tokens from '../tokens.json'; - -enum WeblateChangeActions { - RESOURCE_UPDATED = 0, - TRANSLATION_COMPLETED = 1, - TRANSLATION_CHANGED = 2, - COMMENT_ADDED = 3, - SUGGESTION_ADDED = 4, - TRANSLATION_ADDED = 5, - SUGGESTION_ACCEPTED = 7, - TRANSLATION_REVERTED = 8, - TRANSLATION_UPLOADED = 9, - COMPONENT_LOCKED = 14, - COMPONENT_UNLOCKED = 15, - CHANGES_COMMITTED = 17, - CHANGES_PUSHED = 18, - REPOSITORY_RESET = 19, - REPOSITORY_MERGED = 20, - REPOSITORY_REBASED = 21, - REPOSITORY_MERGE_FAILED = 22, - REPOSITORY_REBASE_FAILED = 23, - PARSING_FAILED = 24, - TRANSLATION_REMOVED = 25, - SUGGESTION_REMOVED = 26, - MARKED_FOR_EDIT = 37, - COMPONENT_RENAMED = 42, - CONTRIBUTOR_JOINED = 45, - LANGUAGE_ADDED = 48, - COMPONENT_CREATED = 51, - ADD_ON_CONFIGURATION_CHANGED = 61, -} - -const creditRelevantActions: WeblateChangeActions[] = [ - WeblateChangeActions.TRANSLATION_CHANGED, - WeblateChangeActions.TRANSLATION_ADDED, - WeblateChangeActions.TRANSLATION_REVERTED, - WeblateChangeActions.TRANSLATION_UPLOADED, - WeblateChangeActions.TRANSLATION_REMOVED, - WeblateChangeActions.MARKED_FOR_EDIT, -]; - -interface WeblateChangeResult { - translation: string; - action: WeblateChangeActions; - action_name: keyof WeblateChangeActions; - user: string; -} - -interface WeblateChangePayload { - next: string; - results: WeblateChangeResult[]; -} - -interface WeblateUserPayload { - full_name: string; -} - -interface WeblateLanguagePayload { - language: { - name: string; - }; -} - -type Username = string; -type UserUrl = string; - -type LanguageName = string; -type TranslationUrl = string; - -type UsernameByUserUrl = Record; -type LanguageNameByTranslationUrl = Record; -type UserUrlsByTranslationUrl = Record; -type ActionByTranslationUrlByUserUrl = Record< - UserUrl, - Record }> ->; - -interface Contributor { - username: Username; - contributionCount: number; -} - -type ContributionsByLanguage = Record; - -export const TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT = { - requiredContributionCount: 10, - keepKnownContributors: true, -}; - -const fetchData = async (url: string, authToken: string): Promise => { - const response = await fetch(url, { - method: 'GET', - headers: { - Authorization: `Token ${authToken}`, - Accept: 'application/json', - }, - }); - - if (!response.ok) { - throw new Error(`Weblate request failed with status ${response.status} - ${response.statusText}`); - } - - return response.json(); -}; - -const fetchWeblateChanges = async ( - url: string, - authToken: string, - weblateChangeResults: WeblateChangeResult[] = [], -): Promise => { - const weblateChangePage = await fetchData(url, authToken); - - if (!weblateChangePage.next) { - return [...weblateChangePage.results, ...weblateChangeResults]; - } - - return fetchWeblateChanges(weblateChangePage.next, authToken, [ - ...weblateChangePage.results, - ...weblateChangeResults, - ]); -}; - -const getUsername = async (url: string, authToken: string): Promise => { - const userPayload = await fetchData(url, authToken); - return userPayload.full_name; -}; - -const getLanguageName = async (url: string, authToken: string): Promise => { - const languagePayload = await fetchData(url, authToken); - return languagePayload.language.name.replace(/\(([a-zA-Z]+) Han script\)/g, '($1)'); -}; +import { + ActionByTranslationUrlByUserUrl, + ContributionsByLanguage, + LanguageNameByTranslationUrl, + UsernameByUserUrl, + UserUrlsByTranslationUrl, + WeblateChangeResult, +} from '../weblate/Weblate.types.ts'; +import { fetchWeblateChanges, getLanguageName, getUsername, validateWeblateDates } from '../weblate/Weblate.utils.ts'; +import { creditRelevantActions, TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT } from '../weblate/Weblate.constants.ts'; const extractContributionInfoFromChanges = ( changes: WeblateChangeResult[], @@ -305,17 +185,6 @@ const getKnownContributorsByLanguage = async (): Promise !!date.match(dateRegex); - -export const validateWeblateDates = (afterDate: string, beforeDate: string) => { - if (!isValidIS08601Date(afterDate) || !isValidIS08601Date(beforeDate)) { - throw new Error( - `The passed timestamps are not properly formatted. They have to match "${dateRegex}" (e.g. 2024-05-11)`, - ); - } -}; - export const createTranslationChangelog = async ( afterDate: string, beforeDate: string, diff --git a/tools/scripts/weblate/Weblate.constants.ts b/tools/scripts/weblate/Weblate.constants.ts new file mode 100644 index 00000000..09955749 --- /dev/null +++ b/tools/scripts/weblate/Weblate.constants.ts @@ -0,0 +1,23 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { WeblateChangeActions } from '@/../tools/scripts/weblate/Weblate.types'; + +export const creditRelevantActions: WeblateChangeActions[] = [ + WeblateChangeActions.TRANSLATION_CHANGED, + WeblateChangeActions.TRANSLATION_ADDED, + WeblateChangeActions.TRANSLATION_REVERTED, + WeblateChangeActions.TRANSLATION_UPLOADED, + WeblateChangeActions.TRANSLATION_REMOVED, + WeblateChangeActions.MARKED_FOR_EDIT, +]; + +export const TRANSLATION_CHANGELOG_YARG_OPTIONS_DEFAULT = { + requiredContributionCount: 10, + keepKnownContributors: true, +}; diff --git a/tools/scripts/weblate/Weblate.types.ts b/tools/scripts/weblate/Weblate.types.ts new file mode 100644 index 00000000..e0d31d2e --- /dev/null +++ b/tools/scripts/weblate/Weblate.types.ts @@ -0,0 +1,80 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +export enum WeblateChangeActions { + RESOURCE_UPDATED = 0, + TRANSLATION_COMPLETED = 1, + TRANSLATION_CHANGED = 2, + COMMENT_ADDED = 3, + SUGGESTION_ADDED = 4, + TRANSLATION_ADDED = 5, + SUGGESTION_ACCEPTED = 7, + TRANSLATION_REVERTED = 8, + TRANSLATION_UPLOADED = 9, + COMPONENT_LOCKED = 14, + COMPONENT_UNLOCKED = 15, + CHANGES_COMMITTED = 17, + CHANGES_PUSHED = 18, + REPOSITORY_RESET = 19, + REPOSITORY_MERGED = 20, + REPOSITORY_REBASED = 21, + REPOSITORY_MERGE_FAILED = 22, + REPOSITORY_REBASE_FAILED = 23, + PARSING_FAILED = 24, + TRANSLATION_REMOVED = 25, + SUGGESTION_REMOVED = 26, + MARKED_FOR_EDIT = 37, + COMPONENT_RENAMED = 42, + CONTRIBUTOR_JOINED = 45, + LANGUAGE_ADDED = 48, + COMPONENT_CREATED = 51, + ADD_ON_CONFIGURATION_CHANGED = 61, +} + +export interface WeblateChangeResult { + translation: string; + action: WeblateChangeActions; + action_name: keyof WeblateChangeActions; + user: string; +} + +export interface WeblateChangePayload { + next: string; + results: WeblateChangeResult[]; +} + +export interface WeblateUserPayload { + full_name: string; +} + +export interface WeblateLanguagePayload { + language: { + name: string; + }; +} + +export type Username = string; +export type UserUrl = string; + +export type LanguageName = string; +export type TranslationUrl = string; + +export type UsernameByUserUrl = Record; +export type LanguageNameByTranslationUrl = Record; +export type UserUrlsByTranslationUrl = Record; +export type ActionByTranslationUrlByUserUrl = Record< + UserUrl, + Record }> +>; + +export interface Contributor { + username: Username; + contributionCount: number; +} + +export type ContributionsByLanguage = Record; diff --git a/tools/scripts/weblate/Weblate.utils.ts b/tools/scripts/weblate/Weblate.utils.ts new file mode 100644 index 00000000..f8c10e50 --- /dev/null +++ b/tools/scripts/weblate/Weblate.utils.ts @@ -0,0 +1,67 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { + WeblateChangePayload, + WeblateChangeResult, + WeblateLanguagePayload, + WeblateUserPayload, +} from '@/../tools/scripts/weblate/Weblate.types.ts'; + +export const fetchData = async (url: string, authToken: string): Promise => { + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: `Token ${authToken}`, + Accept: 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`Weblate request failed with status ${response.status} - ${response.statusText}`); + } + + return response.json(); +}; + +export const fetchWeblateChanges = async ( + url: string, + authToken: string, + weblateChangeResults: WeblateChangeResult[] = [], +): Promise => { + const weblateChangePage = await fetchData(url, authToken); + + if (!weblateChangePage.next) { + return [...weblateChangePage.results, ...weblateChangeResults]; + } + + return fetchWeblateChanges(weblateChangePage.next, authToken, [ + ...weblateChangePage.results, + ...weblateChangeResults, + ]); +}; + +export const getUsername = async (url: string, authToken: string): Promise => { + const userPayload = await fetchData(url, authToken); + return userPayload.full_name; +}; + +export const getLanguageName = async (url: string, authToken: string): Promise => { + const languagePayload = await fetchData(url, authToken); + return languagePayload.language.name.replace(/\(([a-zA-Z]+) Han script\)/g, '($1)'); +}; + +const dateRegex = /[0-9]{4}-[0-9]{2}-[0-9]{2}/g; +const isValidIS08601Date = (date: string): boolean => !!date.match(dateRegex); +export const validateWeblateDates = (afterDate: string, beforeDate: string) => { + if (!isValidIS08601Date(afterDate) || !isValidIS08601Date(beforeDate)) { + throw new Error( + `The passed timestamps are not properly formatted. They have to match "${dateRegex}" (e.g. 2024-05-11)`, + ); + } +};