diff --git a/block.js b/assets/block.js
similarity index 93%
rename from block.js
rename to assets/block.js
index a00a6cc..c4c2302 100644
--- a/block.js
+++ b/assets/block.js
@@ -180,6 +180,17 @@
}
}, [owner, repo, platform, customDomain, customSiteName]);
+ // 获取显示用的头像 URL(优先仓库头像)
+ const getDisplayAvatarUrl = (repoData) => {
+ if (repoData.repo_avatar_url) {
+ return repoData.repo_avatar_url;
+ }
+ if (repoData.owner && repoData.owner.avatar_url) {
+ return repoData.owner.avatar_url;
+ }
+ return '';
+ };
+
const renderPreview = () => {
if (loading) {
return el('div', { className: 'git-embed-loading' },
@@ -209,18 +220,11 @@
const avatarClass = `git-embed-avatar git-embed-avatar-${avatarSize}`;
const buttonClass = `git-embed-button-${buttonSize}`;
- let downloadUrl = '';
- if (repoData.archive_url) {
- const defaultBranch = repoData.default_branch || 'main';
- if (repoData.platform === 'github') {
- downloadUrl = repoData.archive_url.replace('{archive_format}', 'zipball').replace('{/ref}', `/${defaultBranch}`);
- } else {
- downloadUrl = repoData.archive_url;
- if (downloadUrl.includes('main.zip') && defaultBranch !== 'main') {
- downloadUrl = downloadUrl.replace('main.zip', `${defaultBranch}.zip`);
- }
- }
- }
+ // 使用简化的下载地址
+ const downloadUrl = repoData.archive_url || '';
+
+ // 获取显示头像
+ const displayAvatarUrl = getDisplayAvatarUrl(repoData);
return el('div', { className: cardClass },
showSiteInfo && repoData.site_info && el('div', {
@@ -243,10 +247,11 @@
el('div', { className: 'git-embed-header' },
el('div', { className: 'git-embed-title-section' },
- showAvatar && repoData.owner && el('img', {
- src: repoData.owner.avatar_url,
- alt: repoData.owner.login,
- className: avatarClass
+ showAvatar && displayAvatarUrl && el('img', {
+ src: displayAvatarUrl,
+ alt: repoData.name,
+ className: avatarClass,
+ title: repoData.repo_avatar_url ? 'Repository Avatar' : 'Owner Avatar'
}),
el('div', { className: 'git-embed-title-content' },
el('h3', { className: 'git-embed-title' },
@@ -264,7 +269,13 @@
target: '_blank',
rel: 'noopener',
className: 'git-embed-owner-link'
- }, `@${repoData.owner.login}`)
+ }, `@${repoData.owner.login}`),
+ repoData.repo_avatar_url && el('span', {
+ className: 'git-embed-repo-avatar-badge',
+ title: 'Repository has custom avatar'
+ },
+ el('span', { className: 'dashicons dashicons-format-image' })
+ )
)
)
),
@@ -319,10 +330,10 @@
el('span', { className: 'dashicons dashicons-admin-page' }),
'Clone'
),
- showDownloadButton && el('a', {
+ showDownloadButton && downloadUrl && el('a', {
href: downloadUrl,
className: `git-embed-button git-embed-button-secondary ${buttonClass}`,
- download: `${repoData.name}.zip`
+ download: `${repoData.name}-${repoData.default_branch || 'main'}.zip`
},
el('span', { className: 'dashicons dashicons-download' }),
'Download ZIP'
@@ -427,9 +438,10 @@
onChange: (value) => setAttributes({ showSiteInfo: value })
}),
el(ToggleControl, {
- label: __('Show Owner Avatar', 'git-embed-feicode'),
+ label: __('Show Avatar', 'git-embed-feicode'),
checked: showAvatar,
- onChange: (value) => setAttributes({ showAvatar: value })
+ onChange: (value) => setAttributes({ showAvatar: value }),
+ help: 'Shows repository avatar if available, otherwise owner avatar'
}),
showAvatar && el(SelectControl, {
label: __('Avatar Size', 'git-embed-feicode'),
diff --git a/style.css b/assets/style.css
similarity index 98%
rename from style.css
rename to assets/style.css
index e49239e..b5b87fa 100644
--- a/style.css
+++ b/assets/style.css
@@ -179,21 +179,22 @@
border-radius: 15%;
flex-shrink: 0;
border: 2px solid #e1e4e8;
+ padding: 8px;
}
.git-embed-avatar-small {
- width: 32px;
- height: 32px;
+ width: 26px;
+ height: 26px;
}
.git-embed-avatar-medium {
- width: 40px;
- height: 40px;
+ width: 30px;
+ height: 30px;
}
.git-embed-avatar-large {
- width: 48px;
- height: 48px;
+ width: 35px;
+ height: 35px;
}
.git-embed-owner-info {
@@ -287,6 +288,7 @@
.git-embed-description .dashicons {
margin-top: 2px;
flex-shrink: 0;
+ font-size: 18px;
}
.git-embed-stats {
@@ -513,13 +515,13 @@ line-height: 1.25;
}
.git-embed-avatar-large {
- width: 40px;
- height: 40px;
+ width: 30px;
+ height: 30px;
}
.git-embed-avatar-medium {
- width: 32px;
- height: 32px;
+ width: 26px;
+ height: 26px;
}
.git-embed-owner-info {
diff --git a/git-embed-feicode.php b/git-embed-feicode.php
index 49d921c..86e9845 100644
--- a/git-embed-feicode.php
+++ b/git-embed-feicode.php
@@ -3,647 +3,1009 @@ declare(strict_types=1);
/**
* Plugin Name: Git Embed for feiCode
- * Description: Embed Git repositories from GitHub with beautiful cards
- * Version: 1.0.0
+ * Description: Embed Git repositories from GitHub/Gitlab/Gitea/Forgejo and Self-hosted Git service with beautiful cards
+
+
+ * Version: 1.0.1
* Author: feiCode
+ * Author URI: https://cn.feicode.com
* Text Domain: git-embed-feicode
* Domain Path: /languages
+ * Requires at least: 5.0
+ * Tested up to: 6.4
+ * Requires PHP: 7.4
+ * License: GPL v2 or later
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
-if (!defined('ABSPATH')) {
- exit;
+if (!defined("ABSPATH")) {
+ exit();
}
-class GitEmbedFeiCode {
-
- private const PLUGIN_VERSION = '1.0.0';
- private const BLOCK_NAME = 'git-embed-feicode/repository';
-
- public function __construct() {
- add_action('init', [$this, 'init']);
- add_action('wp_ajax_git_embed_fetch', [$this, 'ajax_fetch_repo']);
- add_action('wp_ajax_nopriv_git_embed_fetch', [$this, 'ajax_fetch_repo']);
- add_action('wp_ajax_git_embed_clear_cache', [$this, 'ajax_clear_cache']);
- register_deactivation_hook(__FILE__, [$this, 'clear_all_cache']);
+class GitEmbedFeiCode
+{
+ private const PLUGIN_VERSION = "1.0.1";
+ private const BLOCK_NAME = "git-embed-feicode/repository";
+ private const TEXT_DOMAIN = "git-embed-feicode";
+
+ public function __construct()
+ {
+ add_action("init", [$this, "init"]);
+ add_action("wp_ajax_git_embed_fetch", [$this, "ajax_fetch_repo"]);
+ add_action("wp_ajax_nopriv_git_embed_fetch", [
+ $this,
+ "ajax_fetch_repo",
+ ]);
+ add_action("wp_ajax_git_embed_clear_cache", [
+ $this,
+ "ajax_clear_cache",
+ ]);
+ register_deactivation_hook(__FILE__, [$this, "clear_all_cache"]);
+
+ // 加载语言文件
+ add_action("plugins_loaded", [$this, "load_textdomain"]);
}
-
- public function init(): void {
+
+ public function init(): void
+ {
$this->register_block();
- $this->load_textdomain();
}
-
- private function register_block(): void {
+
+ /**
+ * 加载插件语言文件
+ */
+ public function load_textdomain(): void
+ {
+ load_plugin_textdomain(
+ self::TEXT_DOMAIN,
+ false,
+ dirname(plugin_basename(__FILE__)) . "/languages",
+ );
+ }
+
+ private function register_block(): void
+ {
register_block_type(self::BLOCK_NAME, [
- 'editor_script' => 'git-embed-feicode-editor',
- 'editor_style' => 'git-embed-feicode-style',
- 'style' => 'git-embed-feicode-style',
- 'render_callback' => [$this, 'render_block'],
- 'attributes' => [
- 'platform' => [
- 'type' => 'string',
- 'default' => 'github'
+ "editor_script" => "git-embed-feicode-editor",
+ "editor_style" => "git-embed-feicode-style",
+ "style" => "git-embed-feicode-style",
+ "render_callback" => [$this, "render_block"],
+ "attributes" => [
+ "platform" => [
+ "type" => "string",
+ "default" => "github",
],
- 'customDomain' => [
- 'type' => 'string',
- 'default' => ''
+ "customDomain" => [
+ "type" => "string",
+ "default" => "",
],
- 'customSiteName' => [
- 'type' => 'string',
- 'default' => ''
+ "customSiteName" => [
+ "type" => "string",
+ "default" => "",
],
- 'owner' => [
- 'type' => 'string',
- 'default' => ''
+ "owner" => [
+ "type" => "string",
+ "default" => "",
],
- 'repo' => [
- 'type' => 'string',
- 'default' => ''
+ "repo" => [
+ "type" => "string",
+ "default" => "",
],
- 'showDescription' => [
- 'type' => 'boolean',
- 'default' => true
+ "showDescription" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showStats' => [
- 'type' => 'boolean',
- 'default' => true
+ "showStats" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showLanguage' => [
- 'type' => 'boolean',
- 'default' => true
+ "showLanguage" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showActions' => [
- 'type' => 'boolean',
- 'default' => true
+ "showActions" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showViewButton' => [
- 'type' => 'boolean',
- 'default' => true
+ "showViewButton" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showCloneButton' => [
- 'type' => 'boolean',
- 'default' => true
+ "showCloneButton" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showDownloadButton' => [
- 'type' => 'boolean',
- 'default' => true
+ "showDownloadButton" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'cardStyle' => [
- 'type' => 'string',
- 'default' => 'default'
+ "cardStyle" => [
+ "type" => "string",
+ "default" => "default",
],
- 'buttonStyle' => [
- 'type' => 'string',
- 'default' => 'default'
+ "buttonStyle" => [
+ "type" => "string",
+ "default" => "default",
],
- 'buttonSize' => [
- 'type' => 'string',
- 'default' => 'medium'
+ "buttonSize" => [
+ "type" => "string",
+ "default" => "medium",
],
- 'showIssuesButton' => [
- 'type' => 'boolean',
- 'default' => false
+ "showIssuesButton" => [
+ "type" => "boolean",
+ "default" => false,
],
- 'showForksButton' => [
- 'type' => 'boolean',
- 'default' => false
+ "showForksButton" => [
+ "type" => "boolean",
+ "default" => false,
],
- 'showAvatar' => [
- 'type' => 'boolean',
- 'default' => true
+ "showAvatar" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'showSiteInfo' => [
- 'type' => 'boolean',
- 'default' => true
+ "showSiteInfo" => [
+ "type" => "boolean",
+ "default" => true,
],
- 'avatarSize' => [
- 'type' => 'string',
- 'default' => 'medium'
+ "avatarSize" => [
+ "type" => "string",
+ "default" => "medium",
],
- 'alignment' => [
- 'type' => 'string',
- 'default' => 'none'
- ]
- ]
+ "alignment" => [
+ "type" => "string",
+ "default" => "none",
+ ],
+ ],
]);
-
+
wp_register_script(
- 'git-embed-feicode-editor',
- plugin_dir_url(__FILE__) . 'block.js',
- ['wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n'],
- self::PLUGIN_VERSION
+ "git-embed-feicode-editor",
+ plugin_dir_url(__FILE__) . "assets/block.js",
+ [
+ "wp-blocks",
+ "wp-element",
+ "wp-editor",
+ "wp-components",
+ "wp-i18n",
+ ],
+ self::PLUGIN_VERSION,
);
-
+
wp_register_style(
- 'git-embed-feicode-style',
- plugin_dir_url(__FILE__) . 'style.css',
+ "git-embed-feicode-style",
+ plugin_dir_url(__FILE__) . "assets/style.css",
[],
- self::PLUGIN_VERSION
+ self::PLUGIN_VERSION,
);
-
- wp_localize_script('git-embed-feicode-editor', 'gitEmbedAjax', [
- 'url' => admin_url('admin-ajax.php'),
- 'nonce' => wp_create_nonce('git_embed_nonce'),
- 'cache_nonce' => wp_create_nonce('git_embed_cache_nonce')
+
+ wp_set_script_translations(
+ "git-embed-feicode-editor",
+ self::TEXT_DOMAIN,
+ plugin_dir_path(__FILE__) . "languages",
+ );
+
+ wp_localize_script("git-embed-feicode-editor", "gitEmbedAjax", [
+ "url" => admin_url("admin-ajax.php"),
+ "nonce" => wp_create_nonce("git_embed_nonce"),
+ "cache_nonce" => wp_create_nonce("git_embed_cache_nonce"),
]);
}
-
- public function render_block(array $attributes): string {
- $owner = sanitize_text_field($attributes['owner'] ?? '');
- $repo = sanitize_text_field($attributes['repo'] ?? '');
- $platform = sanitize_text_field($attributes['platform'] ?? 'github');
- $custom_domain = sanitize_text_field($attributes['customDomain'] ?? '');
- $custom_site_name = sanitize_text_field($attributes['customSiteName'] ?? '');
-
+
+ public function render_block(array $attributes): string
+ {
+ $owner = sanitize_text_field($attributes["owner"] ?? "");
+ $repo = sanitize_text_field($attributes["repo"] ?? "");
+ $platform = sanitize_text_field($attributes["platform"] ?? "github");
+ $custom_domain = sanitize_text_field($attributes["customDomain"] ?? "");
+ $custom_site_name = sanitize_text_field(
+ $attributes["customSiteName"] ?? "",
+ );
+
if (empty($owner) || empty($repo)) {
- return '
Repository information required
';
+ return '' .
+ esc_html__(
+ "Repository information required",
+ self::TEXT_DOMAIN,
+ ) .
+ "
";
}
-
- if (in_array($platform, ['gitea', 'forgejo', 'gitlab', 'custom']) && empty($custom_domain)) {
- return 'Custom domain required for ' . ucfirst($platform) . '
';
+
+ if (
+ in_array($platform, ["gitea", "forgejo", "gitlab", "custom"]) &&
+ empty($custom_domain)
+ ) {
+ return '' .
+ esc_html(
+ sprintf(
+ __("Custom domain required for %s", self::TEXT_DOMAIN),
+ ucfirst($platform),
+ ),
+ ) .
+ "
";
}
-
- $repo_data = $this->fetch_repository_data($platform, $owner, $repo, $custom_domain, $custom_site_name);
-
+
+ $repo_data = $this->fetch_repository_data(
+ $platform,
+ $owner,
+ $repo,
+ $custom_domain,
+ $custom_site_name,
+ );
+
if (!$repo_data) {
- return 'Failed to fetch repository data
';
+ return '' .
+ esc_html__(
+ "Failed to fetch repository data",
+ self::TEXT_DOMAIN,
+ ) .
+ "
";
}
-
+
return $this->render_repository_card($repo_data, $attributes);
}
-
- private function fetch_repository_data(string $platform, string $owner, string $repo, string $custom_domain = '', string $custom_site_name = ''): ?array {
- $cache_key = "git_embed_{$platform}_{$owner}_{$repo}" . ($custom_domain ? "_{$custom_domain}" : '') . ($custom_site_name ? "_{$custom_site_name}" : '');
+
+ private function fetch_repository_data(
+ string $platform,
+ string $owner,
+ string $repo,
+ string $custom_domain = "",
+ string $custom_site_name = "",
+ ): ?array {
+ $cache_key =
+ "git_embed_{$platform}_{$owner}_{$repo}" .
+ ($custom_domain ? "_{$custom_domain}" : "") .
+ ($custom_site_name ? "_{$custom_site_name}" : "");
$cached = get_transient($cache_key);
-
+
if ($cached !== false) {
return $cached;
}
-
- $api_config = $this->get_api_config($platform, $custom_domain, $custom_site_name);
+
+ $api_config = $this->get_api_config(
+ $platform,
+ $custom_domain,
+ $custom_site_name,
+ );
if (!$api_config) {
return null;
}
-
- $url = $api_config['api_url'] . "/repos/{$owner}/{$repo}";
+
+ $url = $api_config["api_url"] . "/repos/{$owner}/{$repo}";
$response = wp_remote_get($url, [
- 'timeout' => 15,
- 'headers' => [
- 'User-Agent' => 'Git-Embed-FeiCode/1.0',
- 'Accept' => 'application/json'
- ]
+ "timeout" => 15,
+ "headers" => [
+ "User-Agent" => "Git-Embed-FeiCode/1.0",
+ "Accept" => "application/json",
+ ],
]);
-
- if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
+
+ if (
+ is_wp_error($response) ||
+ wp_remote_retrieve_response_code($response) !== 200
+ ) {
return null;
}
-
+
$data = json_decode(wp_remote_retrieve_body($response), true);
-
+
if (!$data) {
return null;
}
-
- $repo_data = $this->normalize_repository_data($data, $platform, $api_config);
-
- if (!isset($data['default_branch']) && in_array($platform, ['gitea', 'forgejo', 'custom'])) {
- $repo_data['default_branch'] = $this->detect_default_branch($api_config, $owner, $repo);
- $repo_data['archive_url'] = $this->get_archive_url(array_merge($data, ['default_branch' => $repo_data['default_branch']]), $platform, $api_config['base_url']);
+
+ $repo_data = $this->normalize_repository_data(
+ $data,
+ $platform,
+ $api_config,
+ );
+
+ // 获取默认分支和下载地址
+ if (
+ !isset($data["default_branch"]) &&
+ in_array($platform, ["gitea", "forgejo", "custom"])
+ ) {
+ $repo_data["default_branch"] = $this->detect_default_branch(
+ $api_config,
+ $owner,
+ $repo,
+ );
}
-
+
+ // 简化下载地址获取
+ $repo_data["archive_url"] = $this->get_simple_archive_url(
+ $platform,
+ $api_config["base_url"],
+ $owner,
+ $repo,
+ $repo_data["default_branch"],
+ );
+
set_transient($cache_key, $repo_data, DAY_IN_SECONDS);
-
- if (!empty($repo_data['owner']['avatar_url'])) {
- $this->cache_avatar($repo_data['owner']['avatar_url']);
+
+ // 缓存头像(优先仓库头像)
+ if (!empty($repo_data["repo_avatar_url"])) {
+ $this->cache_avatar($repo_data["repo_avatar_url"]);
+ } elseif (!empty($repo_data["owner"]["avatar_url"])) {
+ $this->cache_avatar($repo_data["owner"]["avatar_url"]);
}
-
+
return $repo_data;
}
-
- private function get_api_config(string $platform, string $custom_domain = '', string $custom_site_name = ''): ?array {
+
+ private function get_api_config(
+ string $platform,
+ string $custom_domain = "",
+ string $custom_site_name = "",
+ ): ?array {
switch ($platform) {
- case 'github':
+ case "github":
return [
- 'api_url' => 'https://api.github.com',
- 'base_url' => 'https://github.com',
- 'site_info' => [
- 'name' => 'GitHub',
- 'url' => 'https://github.com',
- 'favicon' => 'https://github.com/favicon.ico',
- 'color' => '#24292f'
- ]
+ "api_url" => "https://api.github.com",
+ "base_url" => "https://github.com",
+ "site_info" => [
+ "name" => "GitHub",
+ "url" => "https://github.com",
+ "favicon" => "https://github.com/favicon.ico",
+ "color" => "#24292f",
+ ],
];
-
- case 'gitea':
+
+ case "gitea":
if (empty($custom_domain)) {
return null;
}
$domain = $this->normalize_domain($custom_domain);
- $site_name = $custom_site_name ?: $this->get_site_name($domain, 'Gitea');
+ $site_name =
+ $custom_site_name ?: $this->get_site_name($domain, "Gitea");
return [
- 'api_url' => "https://{$domain}/api/v1",
- 'base_url' => "https://{$domain}",
- 'site_info' => [
- 'name' => $site_name,
- 'url' => "https://{$domain}",
- 'favicon' => "https://{$domain}/assets/img/favicon.png",
- 'color' => '#609926'
- ]
+ "api_url" => "https://{$domain}/api/v1",
+ "base_url" => "https://{$domain}",
+ "site_info" => [
+ "name" => $site_name,
+ "url" => "https://{$domain}",
+ "favicon" => "https://{$domain}/assets/img/favicon.png",
+ "color" => "#609926",
+ ],
];
-
- case 'forgejo':
+
+ case "forgejo":
if (empty($custom_domain)) {
return null;
}
$domain = $this->normalize_domain($custom_domain);
- $site_name = $custom_site_name ?: $this->get_site_name($domain, 'Forgejo');
+ $site_name =
+ $custom_site_name ?:
+ $this->get_site_name($domain, "Forgejo");
return [
- 'api_url' => "https://{$domain}/api/v1",
- 'base_url' => "https://{$domain}",
- 'site_info' => [
- 'name' => $site_name,
- 'url' => "https://{$domain}",
- 'favicon' => "https://{$domain}/assets/img/favicon.png",
- 'color' => '#fb923c'
- ]
+ "api_url" => "https://{$domain}/api/v1",
+ "base_url" => "https://{$domain}",
+ "site_info" => [
+ "name" => $site_name,
+ "url" => "https://{$domain}",
+ "favicon" => "https://{$domain}/assets/img/favicon.png",
+ "color" => "#fb923c",
+ ],
];
-
- case 'gitlab':
+
+ case "gitlab":
if (empty($custom_domain)) {
return null;
}
$domain = $this->normalize_domain($custom_domain);
- $site_name = $custom_site_name ?: $this->get_site_name($domain, 'GitLab');
+ $site_name =
+ $custom_site_name ?:
+ $this->get_site_name($domain, "GitLab");
return [
- 'api_url' => "https://{$domain}/api/v4",
- 'base_url' => "https://{$domain}",
- 'site_info' => [
- 'name' => $site_name,
- 'url' => "https://{$domain}",
- 'favicon' => "https://{$domain}/assets/favicon.ico",
- 'color' => '#fc6d26'
- ]
+ "api_url" => "https://{$domain}/api/v4",
+ "base_url" => "https://{$domain}",
+ "site_info" => [
+ "name" => $site_name,
+ "url" => "https://{$domain}",
+ "favicon" => "https://{$domain}/assets/favicon.ico",
+ "color" => "#fc6d26",
+ ],
];
-
- case 'custom':
+
+ case "custom":
if (empty($custom_domain)) {
return null;
}
$domain = $this->normalize_domain($custom_domain);
- $site_name = $custom_site_name ?: $this->get_site_name($domain, 'Git Service');
+ $site_name =
+ $custom_site_name ?:
+ $this->get_site_name(
+ $domain,
+ __("Git Service", self::TEXT_DOMAIN),
+ );
return [
- 'api_url' => "https://{$domain}/api/v1",
- 'base_url' => "https://{$domain}",
- 'site_info' => [
- 'name' => $site_name,
- 'url' => "https://{$domain}",
- 'favicon' => "https://{$domain}/favicon.ico",
- 'color' => '#6366f1'
- ]
+ "api_url" => "https://{$domain}/api/v1",
+ "base_url" => "https://{$domain}",
+ "site_info" => [
+ "name" => $site_name,
+ "url" => "https://{$domain}",
+ "favicon" => "https://{$domain}/favicon.ico",
+ "color" => "#6366f1",
+ ],
];
-
+
default:
return null;
}
}
-
- private function get_site_name(string $domain, string $fallback): string {
- $cache_key = 'git_embed_site_name_' . md5($domain);
+
+ private function get_site_name(string $domain, string $fallback): string
+ {
+ $cache_key = "git_embed_site_name_" . md5($domain);
$cached = get_transient($cache_key);
-
+
if ($cached !== false) {
return $cached;
}
-
+
$response = wp_remote_get("https://{$domain}", [
- 'timeout' => 10,
- 'headers' => [
- 'User-Agent' => 'Git-Embed-FeiCode/1.0'
- ]
+ "timeout" => 10,
+ "headers" => [
+ "User-Agent" => "Git-Embed-FeiCode/1.0",
+ ],
]);
-
- if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
+
+ if (
+ !is_wp_error($response) &&
+ wp_remote_retrieve_response_code($response) === 200
+ ) {
$body = wp_remote_retrieve_body($response);
- if (preg_match('/]*>([^<]+)<\/title>/i', $body, $matches)) {
- $title = trim(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'));
+ if (
+ preg_match("/]*>([^<]+)<\/title>/i", $body, $matches)
+ ) {
+ $title = trim(
+ html_entity_decode($matches[1], ENT_QUOTES, "UTF-8"),
+ );
if (!empty($title) && strlen($title) < 100) {
set_transient($cache_key, $title, DAY_IN_SECONDS);
return $title;
}
}
}
-
+
set_transient($cache_key, $fallback, HOUR_IN_SECONDS);
return $fallback;
}
-
- private function detect_default_branch(array $api_config, string $owner, string $repo): string {
- $branches_to_try = ['main', 'master', 'develop', 'dev'];
- $base_url = $api_config['base_url'];
-
+
+ private function detect_default_branch(
+ array $api_config,
+ string $owner,
+ string $repo,
+ ): string {
+ // 尝试从 API 获取默认分支
+ $branches_url =
+ $api_config["api_url"] . "/repos/{$owner}/{$repo}/branches";
+ $response = wp_remote_get($branches_url, [
+ "timeout" => 10,
+ "headers" => [
+ "User-Agent" => "Git-Embed-FeiCode/1.0",
+ "Accept" => "application/json",
+ ],
+ ]);
+
+ if (
+ !is_wp_error($response) &&
+ wp_remote_retrieve_response_code($response) === 200
+ ) {
+ $branches = json_decode(wp_remote_retrieve_body($response), true);
+ if (is_array($branches) && !empty($branches)) {
+ // 查找默认分支(通常是第一个分支或名为 main/master 的分支)
+ foreach ($branches as $branch) {
+ if (isset($branch["name"])) {
+ if (in_array($branch["name"], ["main", "master"])) {
+ return $branch["name"];
+ }
+ }
+ }
+ // 如果没有找到 main/master,返回第一个分支
+ if (isset($branches[0]["name"])) {
+ return $branches[0]["name"];
+ }
+ }
+ }
+
+ // 如果 API 获取失败,尝试通过下载链接测试
+ $branches_to_try = ["main", "master", "develop", "dev"];
+ $base_url = $api_config["base_url"];
+
foreach ($branches_to_try as $branch) {
- $test_url = "{$base_url}/{$owner}/{$repo}/archive/{$branch}.zip";
+ $test_url = "{$base_url}/{$owner}/{$repo}/archive/refs/heads/{$branch}.zip";
$response = wp_remote_head($test_url, [
- 'timeout' => 5,
- 'headers' => [
- 'User-Agent' => 'Git-Embed-FeiCode/1.0'
- ]
+ "timeout" => 5,
+ "headers" => [
+ "User-Agent" => "Git-Embed-FeiCode/1.0",
+ ],
]);
-
- if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
+
+ if (
+ !is_wp_error($response) &&
+ wp_remote_retrieve_response_code($response) === 200
+ ) {
return $branch;
}
}
-
- return 'main';
+
+ return "main";
}
-
- private function normalize_domain(string $domain): string {
+
+ private function normalize_domain(string $domain): string
+ {
$domain = trim($domain);
- $domain = preg_replace('/^https?:\/\//', '', $domain);
- $domain = rtrim($domain, '/');
+ $domain = preg_replace("/^https?:\/\//", "", $domain);
+ $domain = rtrim($domain, "/");
return $domain;
}
-
- private function normalize_repository_data(array $data, string $platform, array $api_config): array {
- $base_url = $api_config['base_url'];
-
- if ($platform === 'gitlab') {
+
+ private function normalize_repository_data(
+ array $data,
+ string $platform,
+ array $api_config,
+ ): array {
+ $base_url = $api_config["base_url"];
+
+ if ($platform === "gitlab") {
return [
- 'name' => $data['name'],
- 'full_name' => $data['path_with_namespace'] ?? ($data['namespace']['name'] . '/' . $data['name']),
- 'description' => $data['description'],
- 'html_url' => $data['web_url'],
- 'language' => $data['language'] ?? null,
- 'stargazers_count' => $data['star_count'] ?? 0,
- 'forks_count' => $data['forks_count'] ?? 0,
- 'open_issues_count' => $data['open_issues_count'] ?? 0,
- 'clone_url' => $data['http_url_to_repo'],
- 'archive_url' => $this->get_archive_url($data, $platform, $base_url),
- 'default_branch' => $data['default_branch'] ?? 'main',
- 'owner' => [
- 'login' => $data['namespace']['name'] ?? $data['owner']['username'],
- 'avatar_url' => $data['namespace']['avatar_url'] ?? $data['owner']['avatar_url'],
- 'html_url' => $base_url . '/' . ($data['namespace']['name'] ?? $data['owner']['username']),
- 'type' => $this->normalize_owner_type($data['namespace']['kind'] ?? $data['owner']['type'] ?? 'user')
+ "name" => $data["name"],
+ "full_name" =>
+ $data["path_with_namespace"] ??
+ $data["namespace"]["name"] . "/" . $data["name"],
+ "description" => $data["description"],
+ "html_url" => $data["web_url"],
+ "language" => $data["language"] ?? null,
+ "stargazers_count" => $data["star_count"] ?? 0,
+ "forks_count" => $data["forks_count"] ?? 0,
+ "open_issues_count" => $data["open_issues_count"] ?? 0,
+ "clone_url" => $data["http_url_to_repo"],
+ "default_branch" => $data["default_branch"] ?? "main",
+ "repo_avatar_url" => $data["avatar_url"] ?? null,
+ "owner" => [
+ "login" =>
+ $data["namespace"]["name"] ??
+ $data["owner"]["username"],
+ "avatar_url" =>
+ $data["namespace"]["avatar_url"] ??
+ $data["owner"]["avatar_url"],
+ "html_url" =>
+ $base_url .
+ "/" .
+ ($data["namespace"]["name"] ??
+ $data["owner"]["username"]),
+ "type" => $this->normalize_owner_type(
+ $data["namespace"]["kind"] ??
+ ($data["owner"]["type"] ?? "user"),
+ ),
],
- 'site_info' => $api_config['site_info'],
- 'platform' => $platform
+ "site_info" => $api_config["site_info"],
+ "platform" => $platform,
];
}
-
+
+ // GitHub, Gitea, Forgejo, Custom
return [
- 'name' => $data['name'],
- 'full_name' => $data['full_name'] ?? ($data['owner']['login'] . '/' . $data['name']),
- 'description' => $data['description'],
- 'html_url' => $data['html_url'],
- 'language' => $data['language'],
- 'stargazers_count' => $data['stargazers_count'] ?? $data['stars_count'] ?? 0,
- 'forks_count' => $data['forks_count'] ?? $data['forks'] ?? 0,
- 'open_issues_count' => $data['open_issues_count'] ?? $data['open_issues'] ?? 0,
- 'clone_url' => $data['clone_url'],
- 'archive_url' => $this->get_archive_url($data, $platform, $base_url),
- 'default_branch' => $data['default_branch'] ?? 'main',
- 'owner' => [
- 'login' => $data['owner']['login'],
- 'avatar_url' => $data['owner']['avatar_url'],
- 'html_url' => $data['owner']['html_url'] ?? $base_url . '/' . $data['owner']['login'],
- 'type' => $this->normalize_owner_type($data['owner']['type'] ?? 'user')
+ "name" => $data["name"],
+ "full_name" =>
+ $data["full_name"] ??
+ $data["owner"]["login"] . "/" . $data["name"],
+ "description" => $data["description"],
+ "html_url" => $data["html_url"],
+ "language" => $data["language"],
+ "stargazers_count" =>
+ $data["stargazers_count"] ?? ($data["stars_count"] ?? 0),
+ "forks_count" => $data["forks_count"] ?? ($data["forks"] ?? 0),
+ "open_issues_count" =>
+ $data["open_issues_count"] ?? ($data["open_issues"] ?? 0),
+ "clone_url" => $data["clone_url"],
+ "default_branch" => $data["default_branch"] ?? "main",
+ "repo_avatar_url" => $data["avatar_url"] ?? null,
+ "owner" => [
+ "login" => $data["owner"]["login"],
+ "avatar_url" => $data["owner"]["avatar_url"],
+ "html_url" =>
+ $data["owner"]["html_url"] ??
+ $base_url . "/" . $data["owner"]["login"],
+ "type" => $this->normalize_owner_type(
+ $data["owner"]["type"] ?? "user",
+ ),
],
- 'site_info' => $api_config['site_info'],
- 'platform' => $platform
+ "site_info" => $api_config["site_info"],
+ "platform" => $platform,
];
}
-
- private function get_archive_url(array $data, string $platform, string $base_url): string {
- if (isset($data['archive_url'])) {
- return $data['archive_url'];
- }
-
- $owner = $data['owner']['login'] ?? $data['namespace']['name'] ?? '';
- $repo = $data['name'];
- $default_branch = $data['default_branch'] ?? 'main';
-
+
+ /**
+ * 简化的下载地址获取方法
+ */
+ private function get_simple_archive_url(
+ string $platform,
+ string $base_url,
+ string $owner,
+ string $repo,
+ string $branch,
+ ): string {
switch ($platform) {
- case 'github':
- return "https://api.github.com/repos/{$owner}/{$repo}/zipball/{archive_format}{/ref}";
- case 'gitlab':
- $project_id = $data['id'] ?? '';
- return "{$base_url}/api/v4/projects/{$project_id}/repository/archive.zip";
- case 'gitea':
- case 'forgejo':
- case 'custom':
- return "{$base_url}/{$owner}/{$repo}/archive/{$default_branch}.zip";
+ case "github":
+ return "https://github.com/{$owner}/{$repo}/archive/refs/heads/{$branch}.zip";
+
+ case "gitlab":
+ return "{$base_url}/{$owner}/{$repo}/-/archive/{$branch}/{$repo}-{$branch}.zip";
+
+ case "gitea":
+ case "forgejo":
+ case "custom":
+ return "{$base_url}/{$owner}/{$repo}/archive/refs/heads/{$branch}.zip";
+
default:
- return '';
+ return "";
}
}
-
- private function normalize_owner_type(string $type): string {
+
+ private function normalize_owner_type(string $type): string
+ {
$type = strtolower(trim($type));
switch ($type) {
- case 'organization':
- case 'org':
- case 'group':
- return 'Organization';
- case 'user':
- case 'individual':
+ case "organization":
+ case "org":
+ case "group":
+ return __("Organization", self::TEXT_DOMAIN);
+ case "user":
+ case "individual":
default:
- return 'User';
+ return __("User", self::TEXT_DOMAIN);
}
}
-
- private function cache_avatar(string $avatar_url): void {
+
+ private function cache_avatar(string $avatar_url): void
+ {
if (empty($avatar_url)) {
return;
}
-
- $cache_key = 'git_embed_avatar_' . md5($avatar_url);
-
+
+ $cache_key = "git_embed_avatar_" . md5($avatar_url);
+
if (get_transient($cache_key) !== false) {
return;
}
-
+
$response = wp_remote_get($avatar_url, [
- 'timeout' => 10,
- 'headers' => [
- 'User-Agent' => 'Git-Embed-FeiCode/1.0'
- ]
+ "timeout" => 10,
+ "headers" => [
+ "User-Agent" => "Git-Embed-FeiCode/1.0",
+ ],
]);
-
- if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
+
+ if (
+ !is_wp_error($response) &&
+ wp_remote_retrieve_response_code($response) === 200
+ ) {
set_transient($cache_key, true, WEEK_IN_SECONDS);
}
}
-
- private function render_repository_card(array $repo_data, array $attributes): string {
- $show_description = $attributes['showDescription'] ?? true;
- $show_stats = $attributes['showStats'] ?? true;
- $show_language = $attributes['showLanguage'] ?? true;
- $show_actions = $attributes['showActions'] ?? true;
- $show_view_button = $attributes['showViewButton'] ?? true;
- $show_clone_button = $attributes['showCloneButton'] ?? true;
- $show_download_button = $attributes['showDownloadButton'] ?? true;
- $show_issues_button = $attributes['showIssuesButton'] ?? false;
- $show_forks_button = $attributes['showForksButton'] ?? false;
- $show_avatar = $attributes['showAvatar'] ?? true;
- $show_site_info = $attributes['showSiteInfo'] ?? true;
- $avatar_size = $attributes['avatarSize'] ?? 'medium';
- $card_style = $attributes['cardStyle'] ?? 'default';
- $button_style = $attributes['buttonStyle'] ?? 'default';
- $button_size = $attributes['buttonSize'] ?? 'medium';
- $alignment = $attributes['alignment'] ?? 'none';
-
- $align_class = $alignment !== 'none' ? " align{$alignment}" : '';
- $card_class = $card_style !== 'default' ? " git-embed-card-{$card_style}" : '';
+
+ /**
+ * 获取显示用的头像 URL(优先仓库头像)
+ */
+ private function get_display_avatar_url(array $repo_data): string
+ {
+ // 优先使用仓库头像
+ if (!empty($repo_data["repo_avatar_url"])) {
+ return $repo_data["repo_avatar_url"];
+ }
+
+ // 其次使用所有者头像
+ if (!empty($repo_data["owner"]["avatar_url"])) {
+ return $repo_data["owner"]["avatar_url"];
+ }
+
+ // 默认头像(可以设置一个占位符头像)
+ return "";
+ }
+
+ private function render_repository_card(
+ array $repo_data,
+ array $attributes,
+ ): string {
+ $show_description = $attributes["showDescription"] ?? true;
+ $show_stats = $attributes["showStats"] ?? true;
+ $show_language = $attributes["showLanguage"] ?? true;
+ $show_actions = $attributes["showActions"] ?? true;
+ $show_view_button = $attributes["showViewButton"] ?? true;
+ $show_clone_button = $attributes["showCloneButton"] ?? true;
+ $show_download_button = $attributes["showDownloadButton"] ?? true;
+ $show_issues_button = $attributes["showIssuesButton"] ?? false;
+ $show_forks_button = $attributes["showForksButton"] ?? false;
+ $show_avatar = $attributes["showAvatar"] ?? true;
+ $show_site_info = $attributes["showSiteInfo"] ?? true;
+ $avatar_size = $attributes["avatarSize"] ?? "medium";
+ $card_style = $attributes["cardStyle"] ?? "default";
+ $button_style = $attributes["buttonStyle"] ?? "default";
+ $button_size = $attributes["buttonSize"] ?? "medium";
+ $alignment = $attributes["alignment"] ?? "none";
+
+ $align_class = $alignment !== "none" ? " align{$alignment}" : "";
+ $card_class =
+ $card_style !== "default" ? " git-embed-card-{$card_style}" : "";
$avatar_class = "git-embed-avatar-{$avatar_size}";
$button_class = "git-embed-button-{$button_size}";
-
- $download_url = $this->get_download_url($repo_data);
-
+
+ // 使用简化的下载地址
+ $download_url = $repo_data["archive_url"];
+
+ // 获取显示用的头像(优先仓库头像)
+ $display_avatar_url = $this->get_display_avatar_url($repo_data);
+
ob_start();
?>
-
+
-
-
+
-
-
+
+
-
+
-
+
- Stars:
-
+
+
- Forks:
-
+
+
- Issues:
-
+
+
-
-
+
+
-
+
- fetch_repository_data($platform, $owner, $repo, $custom_domain, $custom_site_name);
-
+
+ $repo_data = $this->fetch_repository_data(
+ $platform,
+ $owner,
+ $repo,
+ $custom_domain,
+ $custom_site_name,
+ );
+
if (!$repo_data) {
- wp_send_json_error('Failed to fetch repository data');
+ wp_send_json_error(
+ __("Failed to fetch repository", self::TEXT_DOMAIN),
+ );
}
-
+
wp_send_json_success($repo_data);
}
-
- public function ajax_clear_cache(): void {
- check_ajax_referer('git_embed_cache_nonce', 'nonce');
-
- if (!current_user_can('manage_options')) {
- wp_send_json_error('Insufficient permissions');
+
+ public function ajax_clear_cache(): void
+ {
+ check_ajax_referer("git_embed_cache_nonce", "nonce");
+
+ if (!current_user_can("manage_options")) {
+ wp_send_json_error(
+ __("Insufficient permissions", self::TEXT_DOMAIN),
+ );
}
-
- $platform = sanitize_text_field($_POST['platform'] ?? '');
- $custom_domain = sanitize_text_field($_POST['customDomain'] ?? '');
- $custom_site_name = sanitize_text_field($_POST['customSiteName'] ?? '');
- $owner = sanitize_text_field($_POST['owner'] ?? '');
- $repo = sanitize_text_field($_POST['repo'] ?? '');
-
+
+ $platform = sanitize_text_field($_POST["platform"] ?? "");
+ $custom_domain = sanitize_text_field($_POST["customDomain"] ?? "");
+ $custom_site_name = sanitize_text_field($_POST["customSiteName"] ?? "");
+ $owner = sanitize_text_field($_POST["owner"] ?? "");
+ $repo = sanitize_text_field($_POST["repo"] ?? "");
+
if (empty($owner) || empty($repo)) {
- wp_send_json_error('Repository information required');
+ wp_send_json_error(
+ __("Repository information required", self::TEXT_DOMAIN),
+ );
}
-
- $this->clear_repository_cache($platform, $owner, $repo, $custom_domain, $custom_site_name);
-
- wp_send_json_success('Cache cleared successfully');
+
+ $this->clear_repository_cache(
+ $platform,
+ $owner,
+ $repo,
+ $custom_domain,
+ $custom_site_name,
+ );
+
+ wp_send_json_success(
+ __("Cache cleared successfully", self::TEXT_DOMAIN),
+ );
}
-
- private function clear_repository_cache(string $platform, string $owner, string $repo, string $custom_domain = '', string $custom_site_name = ''): void {
- $cache_key = "git_embed_{$platform}_{$owner}_{$repo}" . ($custom_domain ? "_{$custom_domain}" : '') . ($custom_site_name ? "_{$custom_site_name}" : '');
+
+ private function clear_repository_cache(
+ string $platform,
+ string $owner,
+ string $repo,
+ string $custom_domain = "",
+ string $custom_site_name = "",
+ ): void {
+ $cache_key =
+ "git_embed_{$platform}_{$owner}_{$repo}" .
+ ($custom_domain ? "_{$custom_domain}" : "") .
+ ($custom_site_name ? "_{$custom_site_name}" : "");
delete_transient($cache_key);
-
+
if ($custom_domain) {
- $site_cache_key = 'git_embed_site_name_' . md5($custom_domain);
+ $site_cache_key = "git_embed_site_name_" . md5($custom_domain);
delete_transient($site_cache_key);
}
-
+
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
- $wpdb->esc_like('_transient_git_embed_avatar_') . '%'
- )
+ $wpdb->esc_like("_transient_git_embed_avatar_") . "%",
+ ),
);
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
- $wpdb->esc_like('_transient_timeout_git_embed_avatar_') . '%'
- )
+ $wpdb->esc_like("_transient_timeout_git_embed_avatar_") . "%",
+ ),
);
}
-
- public function clear_all_cache(): void {
+
+ public function clear_all_cache(): void
+ {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
- $wpdb->esc_like('_transient_git_embed_') . '%'
- )
+ $wpdb->esc_like("_transient_git_embed_") . "%",
+ ),
);
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
- $wpdb->esc_like('_transient_timeout_git_embed_') . '%'
- )
- );
- }
-
- private function load_textdomain(): void {
- load_plugin_textdomain(
- 'git-embed-feicode',
- false,
- dirname(plugin_basename(__FILE__)) . '/languages'
+ $wpdb->esc_like("_transient_timeout_git_embed_") . "%",
+ ),
);
}
}
-new GitEmbedFeiCode();
\ No newline at end of file
+new GitEmbedFeiCode();
diff --git a/languages/git-embed-feicode-zh_CN.json b/languages/git-embed-feicode-zh_CN.json
new file mode 100644
index 0000000..516acec
--- /dev/null
+++ b/languages/git-embed-feicode-zh_CN.json
@@ -0,0 +1,180 @@
+{
+ "domain": "messages",
+ "locale_data": {
+ "messages": {
+ "": {
+ "domain": "messages",
+ "plural_forms": "nplurals=1; plural=0;",
+ "lang": "zh_CN"
+ },
+ "Git Repository": [
+ "Git 仓库"
+ ],
+ "Embed a Git repository with information and stats": [
+ "嵌入包含信息和统计数据的 Git 仓库"
+ ],
+ "Repository Settings": [
+ "仓库设置"
+ ],
+ "Platform": [
+ "平台"
+ ],
+ "Custom Domain": [
+ "自定义域名"
+ ],
+ "Custom Site Name (Optional)": [
+ "自定义站点名称(可选)"
+ ],
+ "Repository Owner": [
+ "仓库所有者"
+ ],
+ "Repository Name": [
+ "仓库名称"
+ ],
+ "Display Options": [
+ "显示选项"
+ ],
+ "Show Site Information": [
+ "显示站点信息"
+ ],
+ "Show Avatar": [
+ "显示头像"
+ ],
+ "Avatar Size": [
+ "头像大小"
+ ],
+ "Show Description": [
+ "显示描述"
+ ],
+ "Show Programming Language": [
+ "显示编程语言"
+ ],
+ "Show Statistics": [
+ "显示统计数据"
+ ],
+ "Show Action Buttons": [
+ "显示操作按钮"
+ ],
+ "Button Options": [
+ "按钮选项"
+ ],
+ "Show View Repository Button": [
+ "显示查看仓库按钮"
+ ],
+ "Show Clone Button": [
+ "显示克隆按钮"
+ ],
+ "Show Download ZIP Button": [
+ "显示下载 ZIP 按钮"
+ ],
+ "Show Issues Button": [
+ "显示议题按钮"
+ ],
+ "Show Forks Button": [
+ "显示分叉按钮"
+ ],
+ "Button Style": [
+ "按钮样式"
+ ],
+ "Button Size": [
+ "按钮大小"
+ ],
+ "Style Options": [
+ "样式选项"
+ ],
+ "Card Style": [
+ "卡片样式"
+ ],
+ "Please enter repository owner and name": [
+ "请输入仓库所有者和名称"
+ ],
+ "Please enter custom domain for %s": [
+ "请输入 %s 的自定义域名"
+ ],
+ "Fetching repository data...": [
+ "正在获取仓库数据..."
+ ],
+ "Failed to fetch repository": [
+ "获取仓库失败"
+ ],
+ "Network error occurred": [
+ "网络错误"
+ ],
+ "Configure your repository details in the sidebar": [
+ "在侧边栏配置您的仓库详情"
+ ],
+ "Fetch Repository": [
+ "获取仓库"
+ ],
+ "Fetching...": [
+ "正在获取..."
+ ],
+ "Self-hosted Git service requires custom domain": [
+ "自托管 Git 服务需要自定义域名"
+ ],
+ "Enter the domain of your %s instance": [
+ "输入您的 %s 实例域名"
+ ],
+ "Override the automatically detected site name": [
+ "覆盖自动检测的站点名称"
+ ],
+ "Shows repository avatar if available, otherwise owner avatar": [
+ "如果可用,显示仓库头像,否则显示所有者头像"
+ ],
+ "GitHub": [
+ "GitHub"
+ ],
+ "Gitea": [
+ "Gitea"
+ ],
+ "Forgejo": [
+ "Forgejo"
+ ],
+ "GitLab (Self-hosted)": [
+ "GitLab(自托管)"
+ ],
+ "Custom Git Service": [
+ "自定义 Git 服务"
+ ],
+ "Small": [
+ "小"
+ ],
+ "Medium": [
+ "中"
+ ],
+ "Large": [
+ "大"
+ ],
+ "Default": [
+ "默认"
+ ],
+ "Primary (Green)": [
+ "主要(绿色)"
+ ],
+ "Secondary (Gray)": [
+ "次要(灰色)"
+ ],
+ "Outline": [
+ "轮廓"
+ ],
+ "Ghost": [
+ "幽灵"
+ ],
+ "Minimal": [
+ "简约"
+ ],
+ "Bordered": [
+ "边框"
+ ],
+ "Shadow": [
+ "阴影"
+ ],
+ "Gradient": [
+ "渐变"
+ ],
+ "Glassmorphism": [
+ "玻璃态"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/languages/git-embed-feicode-zh_CN.mo b/languages/git-embed-feicode-zh_CN.mo
new file mode 100644
index 0000000..a4ccdbb
Binary files /dev/null and b/languages/git-embed-feicode-zh_CN.mo differ
diff --git a/languages/git-embed-feicode-zh_CN.po b/languages/git-embed-feicode-zh_CN.po
new file mode 100644
index 0000000..9930d2d
--- /dev/null
+++ b/languages/git-embed-feicode-zh_CN.po
@@ -0,0 +1,298 @@
+# Copyright (C) 2024 feiCode
+# This file is distributed under the same license as the Git Embed for feiCode plugin.
+# Translators:
+# feiCode Team, 2024
+msgid ""
+msgstr ""
+"Project-Id-Version: Git Embed for feiCode 1.0.1\n"
+"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/git-embed-feicode\n"
+"POT-Creation-Date: 2024-12-31 12:00+0000\n"
+"PO-Revision-Date: 2025-07-31 17:43+0800\n"
+"Last-Translator: feiCode Team\n"
+"Language-Team: Chinese (China)\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 3.6\n"
+
+#. Plugin Name of the plugin/theme
+msgid "Git Embed for feiCode"
+msgstr "Git 仓库嵌入"
+
+#. Plugin Description of the plugin/theme
+msgid "Embed Git repositories from GitHub/Gitlab/Gitea/Forgejo and Self-hosted Git service with beautiful card"
+msgstr "使用精美卡片嵌入 Git 仓库"
+
+#. Author of the plugin/theme
+msgid "feiCode"
+msgstr "菲码源库"
+
+#: block.js:15
+msgid "Git Repository"
+msgstr "Git 仓库"
+
+#: block.js:16
+msgid "Embed a Git repository with information and stats"
+msgstr "嵌入包含信息和统计数据的 Git 仓库"
+
+#: block.js:170
+msgid "Repository Settings"
+msgstr "仓库设置"
+
+#: block.js:174
+msgid "Platform"
+msgstr "平台"
+
+#: block.js:185
+msgid "Custom Domain"
+msgstr "自定义域名"
+
+#: block.js:195
+msgid "Custom Site Name (Optional)"
+msgstr "自定义站点名称(可选)"
+
+#: block.js:205
+msgid "Repository Owner"
+msgstr "仓库所有者"
+
+#: block.js:214
+msgid "Repository Name"
+msgstr "仓库名称"
+
+#: block.js:227
+msgid "Display Options"
+msgstr "显示选项"
+
+#: block.js:231
+msgid "Show Site Information"
+msgstr "显示站点信息"
+
+#: block.js:235
+msgid "Show Avatar"
+msgstr "显示头像"
+
+#: block.js:240
+msgid "Avatar Size"
+msgstr "头像大小"
+
+#: block.js:252
+msgid "Show Description"
+msgstr "显示描述"
+
+#: block.js:256
+msgid "Show Programming Language"
+msgstr "显示编程语言"
+
+#: block.js:260
+msgid "Show Statistics"
+msgstr "显示统计数据"
+
+#: block.js:264
+msgid "Show Action Buttons"
+msgstr "显示操作按钮"
+
+#: block.js:269
+msgid "Button Options"
+msgstr "按钮选项"
+
+#: block.js:273
+msgid "Show View Repository Button"
+msgstr "显示查看仓库按钮"
+
+#: block.js:278
+msgid "Show Clone Button"
+msgstr "显示克隆按钮"
+
+#: block.js:283
+msgid "Show Download ZIP Button"
+msgstr "显示下载 ZIP 按钮"
+
+#: block.js:288
+msgid "Show Issues Button"
+msgstr "显示议题按钮"
+
+#: block.js:293
+msgid "Show Forks Button"
+msgstr "显示分叉按钮"
+
+#: block.js:298
+msgid "Button Style"
+msgstr "按钮样式"
+
+#: block.js:312
+msgid "Button Size"
+msgstr "按钮大小"
+
+#: block.js:326
+msgid "Style Options"
+msgstr "样式选项"
+
+#: block.js:330
+msgid "Card Style"
+msgstr "卡片样式"
+
+#: git-embed-feicode.php:85
+msgid "Repository information required"
+msgstr "需要仓库信息"
+
+#: git-embed-feicode.php:89
+msgid "Custom domain required for %s"
+msgstr "%s 需要自定义域名"
+
+#: git-embed-feicode.php:95
+msgid "Failed to fetch repository data"
+msgstr "获取仓库数据失败"
+
+#: git-embed-feicode.php:672
+msgid "Insufficient permissions"
+msgstr "权限不足"
+
+#: git-embed-feicode.php:684
+msgid "Cache cleared successfully"
+msgstr "缓存清理成功"
+
+#. Error messages
+msgid "Please enter repository owner and name"
+msgstr "请输入仓库所有者和名称"
+
+#, javascript-format
+msgid "Please enter custom domain for %s"
+msgstr "请输入 %s 的自定义域名"
+
+msgid "Fetching repository data..."
+msgstr "正在获取仓库数据..."
+
+msgid "Failed to fetch repository"
+msgstr "获取仓库失败"
+
+msgid "Network error occurred"
+msgstr "网络错误"
+
+msgid "Configure your repository details in the sidebar"
+msgstr "在侧边栏配置您的仓库详情"
+
+msgid "Fetch Repository"
+msgstr "获取仓库"
+
+msgid "Fetching..."
+msgstr "正在获取..."
+
+#. Help texts
+msgid "Self-hosted Git service requires custom domain"
+msgstr "自托管 Git 服务需要自定义域名"
+
+#, javascript-format
+msgid "Enter the domain of your %s instance"
+msgstr "输入您的 %s 实例域名"
+
+msgid "Override the automatically detected site name"
+msgstr "覆盖自动检测的站点名称"
+
+msgid "Shows repository avatar if available, otherwise owner avatar"
+msgstr "如果可用,显示仓库头像,否则显示所有者头像"
+
+#. Button labels
+msgid "View Repository"
+msgstr "查看仓库"
+
+msgid "Clone"
+msgstr "克隆"
+
+msgid "Download ZIP"
+msgstr "下载 ZIP"
+
+msgid "Issues"
+msgstr "议题"
+
+msgid "Forks"
+msgstr "分叉"
+
+msgid "Copied!"
+msgstr "已复制!"
+
+#. Stats labels
+msgid "Stars:"
+msgstr "星标:"
+
+msgid "Forks:"
+msgstr "分叉:"
+
+msgid "Issues:"
+msgstr "议题:"
+
+#. Options
+msgid "GitHub"
+msgstr "GitHub"
+
+msgid "Gitea"
+msgstr "Gitea"
+
+msgid "Forgejo"
+msgstr "Forgejo"
+
+msgid "GitLab (Self-hosted)"
+msgstr "GitLab(自托管)"
+
+msgid "Custom Git Service"
+msgstr "自定义 Git 服务"
+
+msgid "Small"
+msgstr "小"
+
+msgid "Medium"
+msgstr "中"
+
+msgid "Large"
+msgstr "大"
+
+msgid "Default"
+msgstr "默认"
+
+msgid "Primary (Green)"
+msgstr "主要(绿色)"
+
+msgid "Secondary (Gray)"
+msgstr "次要(灰色)"
+
+msgid "Outline"
+msgstr "轮廓"
+
+msgid "Ghost"
+msgstr "幽灵"
+
+msgid "Minimal"
+msgstr "简约"
+
+msgid "Bordered"
+msgstr "边框"
+
+msgid "Shadow"
+msgstr "阴影"
+
+msgid "Gradient"
+msgstr "渐变"
+
+msgid "Glassmorphism"
+msgstr "玻璃态"
+
+#. Tooltips and titles
+msgid "Click to copy clone URL"
+msgstr "点击复制克隆链接"
+
+msgid "Repository Avatar"
+msgstr "仓库头像"
+
+msgid "Owner Avatar"
+msgstr "所有者头像"
+
+msgid "Repository has custom avatar"
+msgstr "仓库有自定义头像"
+
+#. Owner types
+msgid "Organization"
+msgstr "组织"
+
+msgid "User"
+msgstr "用户"
diff --git a/languages/git-embed-feicode-zh_CN.pot b/languages/git-embed-feicode-zh_CN.pot
new file mode 100644
index 0000000..6ad0c21
--- /dev/null
+++ b/languages/git-embed-feicode-zh_CN.pot
@@ -0,0 +1,296 @@
+# Copyright (C) 2024 feiCode
+# This file is distributed under the same license as the Git Embed for feiCode plugin.
+msgid ""
+msgstr ""
+"Project-Id-Version: Git Embed for feiCode 1.0.1\n"
+"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/git-embed-feicode\n"
+"POT-Creation-Date: 2024-12-31 12:00+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME
\n"
+"Language-Team: LANGUAGE \n"
+"X-Generator: WordPress POT Generator\n"
+
+#. Plugin Name of the plugin/theme
+msgid "Git Embed for feiCode"
+msgstr ""
+
+#. Plugin Description of the plugin/theme
+msgid "Embed Git repositories from GitHub/Gitlab/Gitea/Forgejo and Self-hosted Git service with beautiful cards
+
+"
+msgstr ""
+
+#. Author of the plugin/theme
+msgid "feiCode"
+msgstr ""
+
+#: block.js:15
+msgid "Git Repository"
+msgstr ""
+
+#: block.js:16
+msgid "Embed a Git repository with information and stats"
+msgstr ""
+
+#: block.js:170
+msgid "Repository Settings"
+msgstr ""
+
+#: block.js:174
+msgid "Platform"
+msgstr ""
+
+#: block.js:185
+msgid "Custom Domain"
+msgstr ""
+
+#: block.js:195
+msgid "Custom Site Name (Optional)"
+msgstr ""
+
+#: block.js:205
+msgid "Repository Owner"
+msgstr ""
+
+#: block.js:214
+msgid "Repository Name"
+msgstr ""
+
+#: block.js:227
+msgid "Display Options"
+msgstr ""
+
+#: block.js:231
+msgid "Show Site Information"
+msgstr ""
+
+#: block.js:235
+msgid "Show Avatar"
+msgstr ""
+
+#: block.js:240
+msgid "Avatar Size"
+msgstr ""
+
+#: block.js:252
+msgid "Show Description"
+msgstr ""
+
+#: block.js:256
+msgid "Show Programming Language"
+msgstr ""
+
+#: block.js:260
+msgid "Show Statistics"
+msgstr ""
+
+#: block.js:264
+msgid "Show Action Buttons"
+msgstr ""
+
+#: block.js:269
+msgid "Button Options"
+msgstr ""
+
+#: block.js:273
+msgid "Show View Repository Button"
+msgstr ""
+
+#: block.js:278
+msgid "Show Clone Button"
+msgstr ""
+
+#: block.js:283
+msgid "Show Download ZIP Button"
+msgstr ""
+
+#: block.js:288
+msgid "Show Issues Button"
+msgstr ""
+
+#: block.js:293
+msgid "Show Forks Button"
+msgstr ""
+
+#: block.js:298
+msgid "Button Style"
+msgstr ""
+
+#: block.js:312
+msgid "Button Size"
+msgstr ""
+
+#: block.js:326
+msgid "Style Options"
+msgstr ""
+
+#: block.js:330
+msgid "Card Style"
+msgstr ""
+
+#: git-embed-feicode.php:85
+msgid "Repository information required"
+msgstr ""
+
+#: git-embed-feicode.php:89
+msgid "Custom domain required for %s"
+msgstr ""
+
+#: git-embed-feicode.php:95
+msgid "Failed to fetch repository data"
+msgstr ""
+
+#: git-embed-feicode.php:672
+msgid "Insufficient permissions"
+msgstr ""
+
+#: git-embed-feicode.php:684
+msgid "Cache cleared successfully"
+msgstr ""
+
+#. Error messages
+msgid "Please enter repository owner and name"
+msgstr ""
+
+#, javascript-format
+msgid "Please enter custom domain for %s"
+msgstr ""
+
+msgid "Fetching repository data..."
+msgstr ""
+
+msgid "Failed to fetch repository"
+msgstr ""
+
+msgid "Network error occurred"
+msgstr ""
+
+msgid "Configure your repository details in the sidebar"
+msgstr ""
+
+msgid "Fetch Repository"
+msgstr ""
+
+msgid "Fetching..."
+msgstr ""
+
+#. Help texts
+msgid "Self-hosted Git service requires custom domain"
+msgstr ""
+
+#, javascript-format
+msgid "Enter the domain of your %s instance"
+msgstr ""
+
+msgid "Override the automatically detected site name"
+msgstr ""
+
+msgid "Shows repository avatar if available, otherwise owner avatar"
+msgstr ""
+
+#. Button labels
+msgid "View Repository"
+msgstr ""
+
+msgid "Clone"
+msgstr ""
+
+msgid "Download ZIP"
+msgstr ""
+
+msgid "Issues"
+msgstr ""
+
+msgid "Forks"
+msgstr ""
+
+msgid "Copied!"
+msgstr ""
+
+#. Stats labels
+msgid "Stars:"
+msgstr ""
+
+msgid "Forks:"
+msgstr ""
+
+msgid "Issues:"
+msgstr ""
+
+#. Options
+msgid "GitHub"
+msgstr ""
+
+msgid "Gitea"
+msgstr ""
+
+msgid "Forgejo"
+msgstr ""
+
+msgid "GitLab (Self-hosted)"
+msgstr ""
+
+msgid "Custom Git Service"
+msgstr ""
+
+msgid "Small"
+msgstr ""
+
+msgid "Medium"
+msgstr ""
+
+msgid "Large"
+msgstr ""
+
+msgid "Default"
+msgstr ""
+
+msgid "Primary (Green)"
+msgstr ""
+
+msgid "Secondary (Gray)"
+msgstr ""
+
+msgid "Outline"
+msgstr ""
+
+msgid "Ghost"
+msgstr ""
+
+msgid "Minimal"
+msgstr ""
+
+msgid "Bordered"
+msgstr ""
+
+msgid "Shadow"
+msgstr ""
+
+msgid "Gradient"
+msgstr ""
+
+msgid "Glassmorphism"
+msgstr ""
+
+#. Tooltips and titles
+msgid "Click to copy clone URL"
+msgstr ""
+
+msgid "Repository Avatar"
+msgstr ""
+
+msgid "Owner Avatar"
+msgstr ""
+
+msgid "Repository has custom avatar"
+msgstr ""
+
+#. Owner types
+msgid "Organization"
+msgstr ""
+
+msgid "User"
+msgstr ""