@rmdes/indiekit-endpoint-github 1.0.6 → 1.1.0
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/index.js +27 -1
- package/lib/controllers/starred.js +118 -0
- package/lib/github-client.js +11 -1
- package/lib/github-graphql.js +131 -0
- package/lib/starred-cache.js +128 -0
- package/lib/starred-sync.js +203 -0
- package/locales/de.json +38 -0
- package/locales/es-419.json +38 -0
- package/locales/es.json +38 -0
- package/locales/fr.json +38 -0
- package/locales/hi.json +38 -0
- package/locales/id.json +38 -0
- package/locales/it.json +38 -0
- package/locales/nl.json +38 -0
- package/locales/pl.json +38 -0
- package/locales/pt-BR.json +38 -0
- package/locales/pt.json +38 -0
- package/locales/sr.json +38 -0
- package/locales/sv.json +38 -0
- package/locales/zh-Hans-CN.json +38 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
|
|
3
5
|
import { activityController } from "./lib/controllers/activity.js";
|
|
4
6
|
import { changelogController } from "./lib/controllers/changelog.js";
|
|
@@ -7,6 +9,7 @@ import { contributionsController } from "./lib/controllers/contributions.js";
|
|
|
7
9
|
import { dashboardController } from "./lib/controllers/dashboard.js";
|
|
8
10
|
import { featuredController } from "./lib/controllers/featured.js";
|
|
9
11
|
import { starsController } from "./lib/controllers/stars.js";
|
|
12
|
+
import { starredController } from "./lib/controllers/starred.js";
|
|
10
13
|
|
|
11
14
|
// Module-level routers (matching Indiekit's endpoint pattern)
|
|
12
15
|
const protectedRouter = express.Router();
|
|
@@ -40,6 +43,10 @@ export default class GitHubEndpoint {
|
|
|
40
43
|
return ["GITHUB_TOKEN"];
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
get localesDirectory() {
|
|
47
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "locales");
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
get navigationItems() {
|
|
44
51
|
return {
|
|
45
52
|
href: this.options.mountPath,
|
|
@@ -71,6 +78,7 @@ export default class GitHubEndpoint {
|
|
|
71
78
|
protectedRouter.get("/contributions", contributionsController.get);
|
|
72
79
|
protectedRouter.get("/activity", activityController.get);
|
|
73
80
|
protectedRouter.get("/featured", featuredController.get);
|
|
81
|
+
protectedRouter.get("/starred/sync", starredController.sync);
|
|
74
82
|
|
|
75
83
|
return protectedRouter;
|
|
76
84
|
}
|
|
@@ -87,6 +95,8 @@ export default class GitHubEndpoint {
|
|
|
87
95
|
publicRouter.get("/api/activity", activityController.api);
|
|
88
96
|
publicRouter.get("/api/featured", featuredController.api);
|
|
89
97
|
publicRouter.get("/api/changelog", changelogController.api);
|
|
98
|
+
publicRouter.get("/api/starred/all", starredController.all);
|
|
99
|
+
publicRouter.get("/api/starred/recent", starredController.recent);
|
|
90
100
|
|
|
91
101
|
return publicRouter;
|
|
92
102
|
}
|
|
@@ -94,8 +104,24 @@ export default class GitHubEndpoint {
|
|
|
94
104
|
init(Indiekit) {
|
|
95
105
|
Indiekit.addEndpoint(this);
|
|
96
106
|
|
|
97
|
-
//
|
|
107
|
+
// Register MongoDB collections for starred cache
|
|
108
|
+
Indiekit.addCollection("github_stars");
|
|
109
|
+
Indiekit.addCollection("github_sync_state");
|
|
110
|
+
|
|
111
|
+
// Store config and DB getter for controller access
|
|
98
112
|
Indiekit.config.application.githubConfig = this.options;
|
|
99
113
|
Indiekit.config.application.githubEndpoint = this.mountPath;
|
|
114
|
+
Indiekit.config.application.getGithubDb = () => Indiekit.database;
|
|
115
|
+
|
|
116
|
+
// Start background sync for starred repos (if token + DB available)
|
|
117
|
+
if (this.options.token && Indiekit.database) {
|
|
118
|
+
import("./lib/starred-sync.js")
|
|
119
|
+
.then(({ startStarredSync }) => {
|
|
120
|
+
startStarredSync(Indiekit, this.options);
|
|
121
|
+
})
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
console.error("[GitHub Stars] Sync scheduler failed to start:", error.message);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
100
126
|
}
|
|
101
127
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starred repositories API controller
|
|
3
|
+
* Serves cached data from MongoDB for Eleventy build + live updates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getAllStars,
|
|
8
|
+
getRecentStars,
|
|
9
|
+
getStarCount,
|
|
10
|
+
getSyncState,
|
|
11
|
+
} from "../starred-cache.js";
|
|
12
|
+
import {
|
|
13
|
+
runIncrementalSync,
|
|
14
|
+
runFullSync,
|
|
15
|
+
getStarredSyncStatus,
|
|
16
|
+
} from "../starred-sync.js";
|
|
17
|
+
|
|
18
|
+
export const starredController = {
|
|
19
|
+
/**
|
|
20
|
+
* GET /api/starred/all — Public JSON API
|
|
21
|
+
* Returns all cached starred repos for Eleventy build
|
|
22
|
+
*/
|
|
23
|
+
async all(request, response, next) {
|
|
24
|
+
try {
|
|
25
|
+
const { application } = request.app.locals;
|
|
26
|
+
const db = application.getGithubDb();
|
|
27
|
+
|
|
28
|
+
if (!db) {
|
|
29
|
+
return response.json({ stars: [], totalCount: 0, lastSync: null });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const collection = db.collection("github_stars");
|
|
33
|
+
const [stars, totalCount, syncState] = await Promise.all([
|
|
34
|
+
getAllStars(collection),
|
|
35
|
+
getStarCount(collection),
|
|
36
|
+
getSyncState(db),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
response.json({
|
|
40
|
+
stars,
|
|
41
|
+
totalCount,
|
|
42
|
+
lastSync: syncState.lastIncrementalSync || syncState.lastFullSync || null,
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
next(error);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* GET /api/starred/recent?since=ISO — Public JSON API
|
|
51
|
+
* Returns stars newer than the given timestamp (for live updates)
|
|
52
|
+
*/
|
|
53
|
+
async recent(request, response, next) {
|
|
54
|
+
try {
|
|
55
|
+
const { application } = request.app.locals;
|
|
56
|
+
const db = application.getGithubDb();
|
|
57
|
+
|
|
58
|
+
const since = request.query.since;
|
|
59
|
+
if (!since) {
|
|
60
|
+
return response.status(400).json({ error: "Missing 'since' query parameter (ISO date)" });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!db) {
|
|
64
|
+
return response.json({ stars: [], totalCount: 0 });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const collection = db.collection("github_stars");
|
|
68
|
+
const [stars, totalCount] = await Promise.all([
|
|
69
|
+
getRecentStars(collection, since),
|
|
70
|
+
getStarCount(collection),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
response.json({ stars, totalCount });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
next(error);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* GET /api/starred/sync — Protected, triggers manual sync
|
|
81
|
+
* Query param: ?full=true for full re-sync
|
|
82
|
+
*/
|
|
83
|
+
async sync(request, response, next) {
|
|
84
|
+
try {
|
|
85
|
+
const { application } = request.app.locals;
|
|
86
|
+
const db = application.getGithubDb();
|
|
87
|
+
const config = application.githubConfig;
|
|
88
|
+
|
|
89
|
+
if (!db) {
|
|
90
|
+
return response.status(503).json({ error: "Database not available" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!config.token) {
|
|
94
|
+
return response.status(400).json({ error: "No GitHub token configured" });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const status = getStarredSyncStatus();
|
|
98
|
+
if (status.syncing) {
|
|
99
|
+
return response.status(409).json({ error: "Sync already in progress" });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isFull = request.query.full === "true";
|
|
103
|
+
|
|
104
|
+
// Run sync in background, respond immediately
|
|
105
|
+
const syncFn = isFull ? runFullSync : runIncrementalSync;
|
|
106
|
+
syncFn(db, config).catch((err) => {
|
|
107
|
+
console.error("[GitHub Stars] Manual sync error:", err.message);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
response.json({
|
|
111
|
+
message: `${isFull ? "Full" : "Incremental"} sync started`,
|
|
112
|
+
currentStatus: getStarredSyncStatus(),
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
next(error);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
package/lib/github-client.js
CHANGED
|
@@ -40,7 +40,17 @@ export class GitHubClient {
|
|
|
40
40
|
const response = await fetch(url, { headers });
|
|
41
41
|
|
|
42
42
|
if (!response.ok) {
|
|
43
|
-
|
|
43
|
+
// Only use fromFetch for JSON error responses; GitHub sometimes returns
|
|
44
|
+
// HTML error pages (e.g., 502 Bad Gateway) which cause SyntaxError noise
|
|
45
|
+
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
if (contentType.includes("json")) {
|
|
47
|
+
throw await IndiekitError.fromFetch(response);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new IndiekitError(response.statusText, {
|
|
51
|
+
status: response.status,
|
|
52
|
+
code: response.statusText,
|
|
53
|
+
});
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
const data = await response.json();
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub GraphQL API client for fetching all starred repositories
|
|
3
|
+
* Uses cursor-based pagination to fetch 100 repos per request
|
|
4
|
+
* @module github-graphql
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
|
|
8
|
+
|
|
9
|
+
const STARRED_QUERY = `
|
|
10
|
+
query($cursor: String) {
|
|
11
|
+
viewer {
|
|
12
|
+
starredRepositories(first: 100, after: $cursor, orderBy: {field: STARRED_AT, direction: DESC}) {
|
|
13
|
+
totalCount
|
|
14
|
+
edges {
|
|
15
|
+
starredAt
|
|
16
|
+
node {
|
|
17
|
+
nameWithOwner
|
|
18
|
+
name
|
|
19
|
+
description
|
|
20
|
+
url
|
|
21
|
+
primaryLanguage { name }
|
|
22
|
+
stargazerCount
|
|
23
|
+
forkCount
|
|
24
|
+
pushedAt
|
|
25
|
+
isArchived
|
|
26
|
+
owner { avatarUrl login }
|
|
27
|
+
licenseInfo { spdxId name }
|
|
28
|
+
repositoryTopics(first: 10) {
|
|
29
|
+
nodes { topic { name } }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
pageInfo { endCursor hasNextPage }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format a single starred repo edge from GraphQL response
|
|
41
|
+
* @param {object} edge - GraphQL edge with starredAt + node
|
|
42
|
+
* @returns {object} Formatted starred repo
|
|
43
|
+
*/
|
|
44
|
+
function formatStarredRepo(edge) {
|
|
45
|
+
const repo = edge.node;
|
|
46
|
+
return {
|
|
47
|
+
fullName: repo.nameWithOwner,
|
|
48
|
+
name: repo.name,
|
|
49
|
+
description: repo.description || "",
|
|
50
|
+
url: repo.url,
|
|
51
|
+
language: repo.primaryLanguage?.name || null,
|
|
52
|
+
stars: repo.stargazerCount,
|
|
53
|
+
forks: repo.forkCount,
|
|
54
|
+
topics: (repo.repositoryTopics?.nodes || []).map((n) => n.topic.name),
|
|
55
|
+
license: repo.licenseInfo?.spdxId || null,
|
|
56
|
+
archived: repo.isArchived,
|
|
57
|
+
starredAt: edge.starredAt,
|
|
58
|
+
ownerAvatar: repo.owner?.avatarUrl || "",
|
|
59
|
+
ownerLogin: repo.owner?.login || "",
|
|
60
|
+
pushedAt: repo.pushedAt,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fetch all starred repositories via GraphQL pagination
|
|
66
|
+
* @param {string} token - GitHub personal access token (REQUIRED for GraphQL)
|
|
67
|
+
* @param {object} [options] - Fetch options
|
|
68
|
+
* @param {number} [options.maxPages] - Max pages to fetch (null = all)
|
|
69
|
+
* @param {Function} [options.onPage] - Callback after each page: (pageNum, totalFetched, totalCount)
|
|
70
|
+
* @returns {Promise<{stars: Array, totalCount: number}>}
|
|
71
|
+
*/
|
|
72
|
+
export async function fetchAllStarred(token, options = {}) {
|
|
73
|
+
if (!token) {
|
|
74
|
+
throw new Error("GitHub token is required for GraphQL API");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { maxPages = null, onPage = null } = options;
|
|
78
|
+
const allStars = [];
|
|
79
|
+
let cursor = null;
|
|
80
|
+
let hasNextPage = true;
|
|
81
|
+
let totalCount = 0;
|
|
82
|
+
let pageNum = 0;
|
|
83
|
+
|
|
84
|
+
while (hasNextPage) {
|
|
85
|
+
if (maxPages !== null && pageNum >= maxPages) break;
|
|
86
|
+
|
|
87
|
+
const response = await fetch(GITHUB_GRAPHQL_URL, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${token}`,
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
query: STARRED_QUERY,
|
|
95
|
+
variables: { cursor },
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const body = await response.json();
|
|
104
|
+
|
|
105
|
+
if (body.errors) {
|
|
106
|
+
throw new Error(`GraphQL errors: ${body.errors.map((e) => e.message).join(", ")}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const starred = body.data.viewer.starredRepositories;
|
|
110
|
+
totalCount = starred.totalCount;
|
|
111
|
+
|
|
112
|
+
for (const edge of starred.edges) {
|
|
113
|
+
allStars.push(formatStarredRepo(edge));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
cursor = starred.pageInfo.endCursor;
|
|
117
|
+
hasNextPage = starred.pageInfo.hasNextPage;
|
|
118
|
+
pageNum++;
|
|
119
|
+
|
|
120
|
+
if (onPage) {
|
|
121
|
+
onPage(pageNum, allStars.length, totalCount);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Small delay between pages to avoid secondary rate limits
|
|
125
|
+
if (hasNextPage) {
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { stars: allStars, totalCount };
|
|
131
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB cache layer for starred repositories
|
|
3
|
+
* Follows the webmention-io pattern: receives db object, not Indiekit instance
|
|
4
|
+
* @module starred-cache
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ensure required indexes exist on the github_stars collection
|
|
9
|
+
* @param {import("mongodb").Collection} collection
|
|
10
|
+
*/
|
|
11
|
+
export async function ensureIndexes(collection) {
|
|
12
|
+
await collection.createIndex({ fullName: 1 }, { unique: true });
|
|
13
|
+
await collection.createIndex({ starredAt: -1 });
|
|
14
|
+
await collection.createIndex({ language: 1 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Bulk upsert starred repos into the collection
|
|
19
|
+
* Uses fullName as unique key
|
|
20
|
+
* @param {import("mongodb").Collection} collection
|
|
21
|
+
* @param {Array} stars - Array of formatted starred repos
|
|
22
|
+
* @returns {Promise<{upserted: number, modified: number}>}
|
|
23
|
+
*/
|
|
24
|
+
export async function syncStars(collection, stars) {
|
|
25
|
+
if (!stars || stars.length === 0) return { upserted: 0, modified: 0 };
|
|
26
|
+
|
|
27
|
+
const operations = stars.map((star) => ({
|
|
28
|
+
updateOne: {
|
|
29
|
+
filter: { fullName: star.fullName },
|
|
30
|
+
update: {
|
|
31
|
+
$set: {
|
|
32
|
+
...star,
|
|
33
|
+
updatedAt: new Date().toISOString(),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
upsert: true,
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Process in batches of 500 to avoid MongoDB limits
|
|
41
|
+
let upserted = 0;
|
|
42
|
+
let modified = 0;
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < operations.length; i += 500) {
|
|
45
|
+
const batch = operations.slice(i, i + 500);
|
|
46
|
+
const result = await collection.bulkWrite(batch, { ordered: false });
|
|
47
|
+
upserted += result.upsertedCount;
|
|
48
|
+
modified += result.modifiedCount;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { upserted, modified };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Remove starred repos that no longer exist in the full list
|
|
56
|
+
* Called during full sync to handle unstarred repos
|
|
57
|
+
* @param {import("mongodb").Collection} collection
|
|
58
|
+
* @param {Set<string>} currentFullNames - Set of all current starred repo fullNames
|
|
59
|
+
* @returns {Promise<number>} Number of removed repos
|
|
60
|
+
*/
|
|
61
|
+
export async function removeUnstarred(collection, currentFullNames) {
|
|
62
|
+
const result = await collection.deleteMany({
|
|
63
|
+
fullName: { $nin: [...currentFullNames] },
|
|
64
|
+
});
|
|
65
|
+
return result.deletedCount;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get all starred repos sorted by starredAt descending
|
|
70
|
+
* @param {import("mongodb").Collection} collection
|
|
71
|
+
* @returns {Promise<Array>}
|
|
72
|
+
*/
|
|
73
|
+
export async function getAllStars(collection) {
|
|
74
|
+
return collection
|
|
75
|
+
.find({}, { projection: { _id: 0, updatedAt: 0 } })
|
|
76
|
+
.sort({ starredAt: -1 })
|
|
77
|
+
.toArray();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get stars added since a given date
|
|
82
|
+
* @param {import("mongodb").Collection} collection
|
|
83
|
+
* @param {string} since - ISO 8601 date string
|
|
84
|
+
* @returns {Promise<Array>}
|
|
85
|
+
*/
|
|
86
|
+
export async function getRecentStars(collection, since) {
|
|
87
|
+
return collection
|
|
88
|
+
.find(
|
|
89
|
+
{ starredAt: { $gt: since } },
|
|
90
|
+
{ projection: { _id: 0, updatedAt: 0 } },
|
|
91
|
+
)
|
|
92
|
+
.sort({ starredAt: -1 })
|
|
93
|
+
.toArray();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get total count of starred repos in cache
|
|
98
|
+
* @param {import("mongodb").Collection} collection
|
|
99
|
+
* @returns {Promise<number>}
|
|
100
|
+
*/
|
|
101
|
+
export async function getStarCount(collection) {
|
|
102
|
+
return collection.countDocuments();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get sync state from github_sync_state collection
|
|
107
|
+
* @param {import("mongodb").Db} db
|
|
108
|
+
* @returns {Promise<object>}
|
|
109
|
+
*/
|
|
110
|
+
export async function getSyncState(db) {
|
|
111
|
+
const stateCollection = db.collection("github_sync_state");
|
|
112
|
+
const state = await stateCollection.findOne({ _id: "starred_sync" });
|
|
113
|
+
return state || { lastIncrementalSync: null, lastFullSync: null };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update sync state
|
|
118
|
+
* @param {import("mongodb").Db} db
|
|
119
|
+
* @param {object} update - Fields to update
|
|
120
|
+
*/
|
|
121
|
+
export async function updateSyncState(db, update) {
|
|
122
|
+
const stateCollection = db.collection("github_sync_state");
|
|
123
|
+
await stateCollection.findOneAndUpdate(
|
|
124
|
+
{ _id: "starred_sync" },
|
|
125
|
+
{ $set: update },
|
|
126
|
+
{ upsert: true },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background sync scheduler for starred repositories
|
|
3
|
+
* Follows the webmention-io startSync/stopSync pattern
|
|
4
|
+
* @module starred-sync
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { fetchAllStarred } from "./github-graphql.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureIndexes,
|
|
10
|
+
syncStars,
|
|
11
|
+
removeUnstarred,
|
|
12
|
+
getStarCount,
|
|
13
|
+
getSyncState,
|
|
14
|
+
updateSyncState,
|
|
15
|
+
} from "./starred-cache.js";
|
|
16
|
+
|
|
17
|
+
const SIX_HOURS = 6 * 60 * 60 * 1000;
|
|
18
|
+
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
let incrementalTimer = null;
|
|
21
|
+
let fullSyncTimer = null;
|
|
22
|
+
|
|
23
|
+
let syncStatus = {
|
|
24
|
+
syncing: false,
|
|
25
|
+
lastSync: null,
|
|
26
|
+
lastError: null,
|
|
27
|
+
totalStars: 0,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get current sync status (safe copy for templates/API)
|
|
32
|
+
* @returns {object}
|
|
33
|
+
*/
|
|
34
|
+
export function getStarredSyncStatus() {
|
|
35
|
+
return { ...syncStatus };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Start the background sync scheduler
|
|
40
|
+
* @param {object} Indiekit - Indiekit instance
|
|
41
|
+
* @param {object} options - Plugin options (needs token, username)
|
|
42
|
+
*/
|
|
43
|
+
export function startStarredSync(Indiekit, options) {
|
|
44
|
+
if (!options.token) {
|
|
45
|
+
console.warn("[GitHub Stars] No token configured, starred sync disabled");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const db = Indiekit.database;
|
|
50
|
+
if (!db) {
|
|
51
|
+
console.warn("[GitHub Stars] No database available, starred sync disabled");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log("[GitHub Stars] Starting background sync");
|
|
56
|
+
|
|
57
|
+
// Initial sync after 15s delay (let other plugins init first)
|
|
58
|
+
setTimeout(async () => {
|
|
59
|
+
try {
|
|
60
|
+
const collection = db.collection("github_stars");
|
|
61
|
+
await ensureIndexes(collection);
|
|
62
|
+
|
|
63
|
+
const count = await getStarCount(collection);
|
|
64
|
+
if (count === 0) {
|
|
65
|
+
console.log("[GitHub Stars] Empty cache, running full sync...");
|
|
66
|
+
await runFullSync(db, options);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`[GitHub Stars] Cache has ${count} stars, running incremental sync...`);
|
|
69
|
+
await runIncrementalSync(db, options);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("[GitHub Stars] Initial sync error:", error.message);
|
|
73
|
+
}
|
|
74
|
+
}, 15_000);
|
|
75
|
+
|
|
76
|
+
// Incremental sync every 6 hours
|
|
77
|
+
incrementalTimer = setInterval(() => {
|
|
78
|
+
runIncrementalSync(db, options).catch((err) => {
|
|
79
|
+
console.error("[GitHub Stars] Incremental sync error:", err.message);
|
|
80
|
+
});
|
|
81
|
+
}, SIX_HOURS);
|
|
82
|
+
|
|
83
|
+
// Full sync every 7 days
|
|
84
|
+
fullSyncTimer = setInterval(() => {
|
|
85
|
+
runFullSync(db, options).catch((err) => {
|
|
86
|
+
console.error("[GitHub Stars] Full sync error:", err.message);
|
|
87
|
+
});
|
|
88
|
+
}, SEVEN_DAYS);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stop all sync timers
|
|
93
|
+
*/
|
|
94
|
+
export function stopStarredSync() {
|
|
95
|
+
if (incrementalTimer) {
|
|
96
|
+
clearInterval(incrementalTimer);
|
|
97
|
+
incrementalTimer = null;
|
|
98
|
+
}
|
|
99
|
+
if (fullSyncTimer) {
|
|
100
|
+
clearInterval(fullSyncTimer);
|
|
101
|
+
fullSyncTimer = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run incremental sync (fetch first 2 pages = 200 most recent stars)
|
|
107
|
+
* @param {import("mongodb").Db} db
|
|
108
|
+
* @param {object} options - Plugin options
|
|
109
|
+
* @returns {Promise<object>} Sync result
|
|
110
|
+
*/
|
|
111
|
+
export async function runIncrementalSync(db, options) {
|
|
112
|
+
if (syncStatus.syncing) {
|
|
113
|
+
return { error: "Sync already in progress" };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
syncStatus.syncing = true;
|
|
117
|
+
syncStatus.lastError = null;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const collection = db.collection("github_stars");
|
|
121
|
+
await ensureIndexes(collection);
|
|
122
|
+
|
|
123
|
+
const { stars } = await fetchAllStarred(options.token, { maxPages: 2 });
|
|
124
|
+
|
|
125
|
+
const result = await syncStars(collection, stars);
|
|
126
|
+
const totalStars = await getStarCount(collection);
|
|
127
|
+
|
|
128
|
+
syncStatus.lastSync = new Date().toISOString();
|
|
129
|
+
syncStatus.totalStars = totalStars;
|
|
130
|
+
syncStatus.syncing = false;
|
|
131
|
+
|
|
132
|
+
await updateSyncState(db, {
|
|
133
|
+
lastIncrementalSync: syncStatus.lastSync,
|
|
134
|
+
totalStars,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
console.log(
|
|
138
|
+
`[GitHub Stars] Incremental sync: ${result.upserted} new, ${result.modified} updated, ${totalStars} total`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return { ...result, totalStars };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
syncStatus.lastError = error.message;
|
|
144
|
+
syncStatus.syncing = false;
|
|
145
|
+
console.error("[GitHub Stars] Incremental sync failed:", error.message);
|
|
146
|
+
return { error: error.message };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Run full sync (fetch ALL starred repos, remove unstarred)
|
|
152
|
+
* @param {import("mongodb").Db} db
|
|
153
|
+
* @param {object} options - Plugin options
|
|
154
|
+
* @returns {Promise<object>} Sync result
|
|
155
|
+
*/
|
|
156
|
+
export async function runFullSync(db, options) {
|
|
157
|
+
if (syncStatus.syncing) {
|
|
158
|
+
return { error: "Sync already in progress" };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
syncStatus.syncing = true;
|
|
162
|
+
syncStatus.lastError = null;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const collection = db.collection("github_stars");
|
|
166
|
+
await ensureIndexes(collection);
|
|
167
|
+
|
|
168
|
+
const { stars, totalCount } = await fetchAllStarred(options.token, {
|
|
169
|
+
onPage: (page, fetched, total) => {
|
|
170
|
+
console.log(`[GitHub Stars] Full sync: page ${page}, ${fetched}/${total} fetched`);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await syncStars(collection, stars);
|
|
175
|
+
|
|
176
|
+
// Remove repos that were unstarred
|
|
177
|
+
const currentNames = new Set(stars.map((s) => s.fullName));
|
|
178
|
+
const removed = await removeUnstarred(collection, currentNames);
|
|
179
|
+
|
|
180
|
+
const totalStars = await getStarCount(collection);
|
|
181
|
+
|
|
182
|
+
syncStatus.lastSync = new Date().toISOString();
|
|
183
|
+
syncStatus.totalStars = totalStars;
|
|
184
|
+
syncStatus.syncing = false;
|
|
185
|
+
|
|
186
|
+
await updateSyncState(db, {
|
|
187
|
+
lastFullSync: syncStatus.lastSync,
|
|
188
|
+
lastIncrementalSync: syncStatus.lastSync,
|
|
189
|
+
totalStars,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
console.log(
|
|
193
|
+
`[GitHub Stars] Full sync complete: ${result.upserted} new, ${result.modified} updated, ${removed} removed, ${totalStars} total (GitHub reports ${totalCount})`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return { ...result, removed, totalStars, totalCount };
|
|
197
|
+
} catch (error) {
|
|
198
|
+
syncStatus.lastError = error.message;
|
|
199
|
+
syncStatus.syncing = false;
|
|
200
|
+
console.error("[GitHub Stars] Full sync failed:", error.message);
|
|
201
|
+
return { error: error.message };
|
|
202
|
+
}
|
|
203
|
+
}
|
package/locales/de.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub-Aktivität",
|
|
5
|
+
"viewAll": "Alle anzeigen",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Kein GitHub-Benutzername konfiguriert. Fügen Sie einen Benutzernamen in Ihrer Indiekit-Konfiguration hinzu."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Ausgewählte Projekte",
|
|
11
|
+
"none": "Keine ausgewählten Repositories konfiguriert"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Aktuelle Commits",
|
|
15
|
+
"none": "Keine aktuellen Commits"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Kürzlich mit Stern versehen",
|
|
19
|
+
"none": "Keine mit Stern versehenen Repositories"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs & Issues",
|
|
23
|
+
"none": "Keine aktuellen Beiträge"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Meine Repositories",
|
|
27
|
+
"none": "Keine Repositories gefunden"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Aktivität in meinen Repos",
|
|
31
|
+
"none": "Keine aktuellen Aktivitäten von anderen"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Zeigen Sie Ihre GitHub-Aktivität, Commits, Sterne und Beiträge an.",
|
|
35
|
+
"view": "Aktivität anzeigen"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Actividad en GitHub",
|
|
5
|
+
"viewAll": "Ver todo",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "No hay un nombre de usuario de GitHub configurado. Agrega un nombre de usuario en tu configuración de Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Proyectos destacados",
|
|
11
|
+
"none": "No hay repositorios destacados configurados"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commits recientes",
|
|
15
|
+
"none": "No hay commits recientes"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Destacados recientemente",
|
|
19
|
+
"none": "No hay repositorios destacados"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs e Issues",
|
|
23
|
+
"none": "No hay contribuciones recientes"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Mis repositorios",
|
|
27
|
+
"none": "No se encontraron repositorios"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Actividad en mis repos",
|
|
31
|
+
"none": "No hay actividad reciente de otros"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Visualiza tu actividad en GitHub, commits, estrellas y contribuciones.",
|
|
35
|
+
"view": "Ver actividad"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/es.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Actividad en GitHub",
|
|
5
|
+
"viewAll": "Ver todo",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "No hay un nombre de usuario de GitHub configurado. Añade un nombre de usuario en tu configuración de Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Proyectos destacados",
|
|
11
|
+
"none": "No hay repositorios destacados configurados"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commits recientes",
|
|
15
|
+
"none": "No hay commits recientes"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Destacados recientemente",
|
|
19
|
+
"none": "No hay repositorios destacados"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs e Issues",
|
|
23
|
+
"none": "No hay contribuciones recientes"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Mis repositorios",
|
|
27
|
+
"none": "No se han encontrado repositorios"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Actividad en mis repos",
|
|
31
|
+
"none": "No hay actividad reciente de otros"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Consulta tu actividad en GitHub, commits, estrellas y contribuciones.",
|
|
35
|
+
"view": "Ver actividad"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/fr.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Activité GitHub",
|
|
5
|
+
"viewAll": "Tout voir",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Aucun nom d'utilisateur GitHub configuré. Ajoutez un nom d'utilisateur dans votre configuration Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Projets en vedette",
|
|
11
|
+
"none": "Aucun dépôt en vedette configuré"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commits récents",
|
|
15
|
+
"none": "Aucun commit récent"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Récemment ajoutés aux favoris",
|
|
19
|
+
"none": "Aucun dépôt en favori"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs et Issues",
|
|
23
|
+
"none": "Aucune contribution récente"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Mes dépôts",
|
|
27
|
+
"none": "Aucun dépôt trouvé"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Activité sur mes dépôts",
|
|
31
|
+
"none": "Aucune activité récente des autres"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Consultez votre activité GitHub, vos commits, favoris et contributions.",
|
|
35
|
+
"view": "Voir l'activité"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/hi.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub गतिविधि",
|
|
5
|
+
"viewAll": "सभी देखें",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "कोई GitHub उपयोगकर्ता नाम कॉन्फ़िगर नहीं किया गया। अपनी Indiekit कॉन्फ़िगरेशन में एक उपयोगकर्ता नाम जोड़ें।"
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "फ़ीचर्ड प्रोजेक्ट",
|
|
11
|
+
"none": "कोई फ़ीचर्ड रिपॉज़िटरी कॉन्फ़िगर नहीं की गई"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "हाल के कमिट",
|
|
15
|
+
"none": "कोई हाल के कमिट नहीं"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "हाल ही में स्टार किए गए",
|
|
19
|
+
"none": "कोई स्टार की गई रिपॉज़िटरी नहीं"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs और Issues",
|
|
23
|
+
"none": "कोई हाल के योगदान नहीं"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "मेरी रिपॉज़िटरी",
|
|
27
|
+
"none": "कोई रिपॉज़िटरी नहीं मिली"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "मेरी repos पर गतिविधि",
|
|
31
|
+
"none": "दूसरों से कोई हाल की गतिविधि नहीं"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "अपनी GitHub गतिविधि, कमिट, स्टार और योगदान देखें।",
|
|
35
|
+
"view": "गतिविधि देखें"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/id.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Aktivitas GitHub",
|
|
5
|
+
"viewAll": "Lihat semua",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Tidak ada nama pengguna GitHub yang dikonfigurasi. Tambahkan nama pengguna di konfigurasi Indiekit Anda."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Proyek Unggulan",
|
|
11
|
+
"none": "Tidak ada repositori unggulan yang dikonfigurasi"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commit terkini",
|
|
15
|
+
"none": "Tidak ada commit terkini"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Baru diberi bintang",
|
|
19
|
+
"none": "Tidak ada repositori berbintang"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR & Issue",
|
|
23
|
+
"none": "Tidak ada kontribusi terkini"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Repositori saya",
|
|
27
|
+
"none": "Tidak ada repositori ditemukan"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Aktivitas di repo saya",
|
|
31
|
+
"none": "Tidak ada aktivitas terkini dari orang lain"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Lihat aktivitas GitHub, commit, bintang, dan kontribusi Anda.",
|
|
35
|
+
"view": "Lihat aktivitas"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/it.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Attività GitHub",
|
|
5
|
+
"viewAll": "Vedi tutto",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Nessun nome utente GitHub configurato. Aggiungi un nome utente nella tua configurazione Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Progetti in evidenza",
|
|
11
|
+
"none": "Nessun repository in evidenza configurato"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commit recenti",
|
|
15
|
+
"none": "Nessun commit recente"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Aggiunti di recente alle stelle",
|
|
19
|
+
"none": "Nessun repository con stelle"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR e Issue",
|
|
23
|
+
"none": "Nessun contributo recente"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "I miei repository",
|
|
27
|
+
"none": "Nessun repository trovato"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Attività sui miei repos",
|
|
31
|
+
"none": "Nessuna attività recente da altri"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Visualizza la tua attività GitHub, commit, stelle e contributi.",
|
|
35
|
+
"view": "Vedi attività"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/nl.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub-activiteit",
|
|
5
|
+
"viewAll": "Alles bekijken",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Geen GitHub-gebruikersnaam geconfigureerd. Voeg een gebruikersnaam toe in je Indiekit-configuratie."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Uitgelichte projecten",
|
|
11
|
+
"none": "Geen uitgelichte repositories geconfigureerd"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Recente commits",
|
|
15
|
+
"none": "Geen recente commits"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Recent met ster gemarkeerd",
|
|
19
|
+
"none": "Geen repositories met ster"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR's & Issues",
|
|
23
|
+
"none": "Geen recente bijdragen"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Mijn repositories",
|
|
27
|
+
"none": "Geen repositories gevonden"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Activiteit op mijn repos",
|
|
31
|
+
"none": "Geen recente activiteit van anderen"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Bekijk je GitHub-activiteit, commits, sterren en bijdragen.",
|
|
35
|
+
"view": "Activiteit bekijken"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/pl.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Aktywność na GitHub",
|
|
5
|
+
"viewAll": "Zobacz wszystkie",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Nie skonfigurowano nazwy użytkownika GitHub. Dodaj nazwę użytkownika w konfiguracji Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Wyróżnione projekty",
|
|
11
|
+
"none": "Nie skonfigurowano wyróżnionych repozytoriów"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Ostatnie commity",
|
|
15
|
+
"none": "Brak ostatnich commitów"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Ostatnio oznaczone gwiazdką",
|
|
19
|
+
"none": "Brak repozytoriów oznaczonych gwiazdką"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR-y i Issues",
|
|
23
|
+
"none": "Brak ostatnich wkładów"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Moje repozytoria",
|
|
27
|
+
"none": "Nie znaleziono repozytoriów"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Aktywność w moich repach",
|
|
31
|
+
"none": "Brak ostatniej aktywności innych"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Zobacz swoją aktywność na GitHub, commity, gwiazdki i wkłady.",
|
|
35
|
+
"view": "Zobacz aktywność"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Atividade no GitHub",
|
|
5
|
+
"viewAll": "Ver tudo",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Nenhum nome de usuário do GitHub configurado. Adicione um nome de usuário na sua configuração do Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Projetos em destaque",
|
|
11
|
+
"none": "Nenhum repositório em destaque configurado"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commits recentes",
|
|
15
|
+
"none": "Nenhum commit recente"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Recentemente destacados",
|
|
19
|
+
"none": "Nenhum repositório com estrela"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs e Issues",
|
|
23
|
+
"none": "Nenhuma contribuição recente"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Meus repositórios",
|
|
27
|
+
"none": "Nenhum repositório encontrado"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Atividade nos meus repos",
|
|
31
|
+
"none": "Nenhuma atividade recente de outros"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Veja sua atividade no GitHub, commits, estrelas e contribuições.",
|
|
35
|
+
"view": "Ver atividade"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/pt.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "Atividade no GitHub",
|
|
5
|
+
"viewAll": "Ver tudo",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Nenhum nome de utilizador do GitHub configurado. Adicione um nome de utilizador na sua configuração do Indiekit."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Projetos em destaque",
|
|
11
|
+
"none": "Nenhum repositório em destaque configurado"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Commits recentes",
|
|
15
|
+
"none": "Nenhum commit recente"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Recentemente destacados",
|
|
19
|
+
"none": "Nenhum repositório com estrela"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PRs e Issues",
|
|
23
|
+
"none": "Nenhuma contribuição recente"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Os meus repositórios",
|
|
27
|
+
"none": "Nenhum repositório encontrado"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Atividade nos meus repos",
|
|
31
|
+
"none": "Nenhuma atividade recente de outros"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Consulte a sua atividade no GitHub, commits, estrelas e contribuições.",
|
|
35
|
+
"view": "Ver atividade"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/sr.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub активност",
|
|
5
|
+
"viewAll": "Прикажи све",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Није конфигурисано корисничко име за GitHub. Додајте корисничко име у вашој Indiekit конфигурацији."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Истакнути пројекти",
|
|
11
|
+
"none": "Нису конфигурисани истакнути репозиторијуми"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Недавни commits",
|
|
15
|
+
"none": "Нема недавних commits"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Недавно означено звездицом",
|
|
19
|
+
"none": "Нема репозиторијума са звездицом"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR-ови и Issues",
|
|
23
|
+
"none": "Нема недавних доприноса"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Моји репозиторијуми",
|
|
27
|
+
"none": "Није пронађен ниједан репозиторијум"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Активност на мојим репоима",
|
|
31
|
+
"none": "Нема недавне активности од других"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Погледајте вашу GitHub активност, commits, звездице и доприносе.",
|
|
35
|
+
"view": "Прикажи активност"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/locales/sv.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub-aktivitet",
|
|
5
|
+
"viewAll": "Visa alla",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "Inget GitHub-användarnamn konfigurerat. Lägg till ett användarnamn i din Indiekit-konfiguration."
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "Utvalda projekt",
|
|
11
|
+
"none": "Inga utvalda arkiv konfigurerade"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "Senaste commits",
|
|
15
|
+
"none": "Inga senaste commits"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "Nyligen stjärnmärkta",
|
|
19
|
+
"none": "Inga stjärnmärkta arkiv"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR:ar & Issues",
|
|
23
|
+
"none": "Inga senaste bidrag"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "Mina arkiv",
|
|
27
|
+
"none": "Inga arkiv hittades"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "Aktivitet i mina repos",
|
|
31
|
+
"none": "Ingen senaste aktivitet från andra"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "Visa din GitHub-aktivitet, commits, stjärnor och bidrag.",
|
|
35
|
+
"view": "Visa aktivitet"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"github": {
|
|
3
|
+
"title": "GitHub",
|
|
4
|
+
"activity": "GitHub 活动",
|
|
5
|
+
"viewAll": "查看全部",
|
|
6
|
+
"error": {
|
|
7
|
+
"noUsername": "未配置 GitHub 用户名。请在您的 Indiekit 配置中添加用户名。"
|
|
8
|
+
},
|
|
9
|
+
"featured": {
|
|
10
|
+
"title": "精选项目",
|
|
11
|
+
"none": "未配置精选仓库"
|
|
12
|
+
},
|
|
13
|
+
"commits": {
|
|
14
|
+
"title": "最近提交",
|
|
15
|
+
"none": "暂无最近提交"
|
|
16
|
+
},
|
|
17
|
+
"stars": {
|
|
18
|
+
"title": "最近加星",
|
|
19
|
+
"none": "暂无加星仓库"
|
|
20
|
+
},
|
|
21
|
+
"contributions": {
|
|
22
|
+
"title": "PR 和 Issue",
|
|
23
|
+
"none": "暂无最近贡献"
|
|
24
|
+
},
|
|
25
|
+
"repos": {
|
|
26
|
+
"title": "我的仓库",
|
|
27
|
+
"none": "未找到仓库"
|
|
28
|
+
},
|
|
29
|
+
"activity": {
|
|
30
|
+
"title": "我的仓库活动",
|
|
31
|
+
"none": "暂无其他人的最近活动"
|
|
32
|
+
},
|
|
33
|
+
"widget": {
|
|
34
|
+
"description": "查看您的 GitHub 活动、提交、星标和贡献。",
|
|
35
|
+
"view": "查看活动"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
CHANGED