Mirror of awesome-nano-banana-pro-prompts tuned for OpenAI's upcoming
GPT Image 2 (codename "duct-tape"):
- Swap CMS model filter to gpt-image-2 + campaign gpt-image-2-prompts
- i18n.ts: all 16 languages rebranded; rewrite seedancePromo to
cross-promote nano-banana-pro, and whatIs content to reflect
GPT Image 2's actual strengths:
* Pixel-perfect multi-language text rendering (zh/en/ja)
* Cross-image pixel-level consistency
* Commercial-ready illustration quality
* True art style induction
Primary languages (en, zh, zh-TW, ja, ko) hand-translated;
others fall back to English copy (will be refined later)
- markdown-generator: cover image path switched to
gpt-image-2-prompts-cover-{en,zh}.png, arenaUrl points at the
gallery page (the side-by-side arena page doesn't exist yet)
- GitHub Actions:
* update-readme.yml runs on cron 0 0,12 * * * (twice daily
per product request, instead of every 4 hours)
* sync-approved-to-cms.yml wired to gpt-image-2 model
- Cover images, issue templates, docs, LICENSE (year 2026) updated
- Initial 16 README_*.md files generated from live CMS (78 prompts,
44 categories) so the repo renders immediately without waiting
for the first Action run
Secrets the repo needs before the Action runs on the remote:
- CMS_HOST
- CMS_API_KEY
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
451 lines
15 KiB
TypeScript
451 lines
15 KiB
TypeScript
import { Prompt, FilterCategory } from './cms-client.js';
|
||
import { t } from './i18n.js';
|
||
|
||
interface SortedPrompts {
|
||
all: Prompt[];
|
||
featured: Prompt[];
|
||
regular: Prompt[];
|
||
stats: {
|
||
total: number;
|
||
featured: number;
|
||
};
|
||
categories?: FilterCategory[];
|
||
}
|
||
|
||
export interface LanguageConfig {
|
||
code: string;
|
||
name: string; // Display name
|
||
readmeFileName: string;
|
||
}
|
||
|
||
export const SUPPORTED_LANGUAGES: LanguageConfig[] = [
|
||
{ code: 'en', name: 'English', readmeFileName: 'README.md' },
|
||
{ code: 'zh', name: '简体中文', readmeFileName: 'README_zh.md' },
|
||
{ code: 'zh-TW', name: '繁體中文', readmeFileName: 'README_zh-TW.md' },
|
||
{ code: 'ja-JP', name: '日本語', readmeFileName: 'README_ja-JP.md' },
|
||
{ code: 'ko-KR', name: '한국어', readmeFileName: 'README_ko-KR.md' },
|
||
{ code: 'th-TH', name: 'ไทย', readmeFileName: 'README_th-TH.md' },
|
||
{ code: 'vi-VN', name: 'Tiếng Việt', readmeFileName: 'README_vi-VN.md' },
|
||
{ code: 'hi-IN', name: 'हिन्दी', readmeFileName: 'README_hi-IN.md' },
|
||
{ code: 'es-ES', name: 'Español', readmeFileName: 'README_es-ES.md' },
|
||
{ code: 'es-419', name: 'Español (Latinoamérica)', readmeFileName: 'README_es-419.md' },
|
||
{ code: 'de-DE', name: 'Deutsch', readmeFileName: 'README_de-DE.md' },
|
||
{ code: 'fr-FR', name: 'Français', readmeFileName: 'README_fr-FR.md' },
|
||
{ code: 'it-IT', name: 'Italiano', readmeFileName: 'README_it-IT.md' },
|
||
{ code: 'pt-BR', name: 'Português (Brasil)', readmeFileName: 'README_pt-BR.md' },
|
||
{ code: 'pt-PT', name: 'Português', readmeFileName: 'README_pt-PT.md' },
|
||
{ code: 'tr-TR', name: 'Türkçe', readmeFileName: 'README_tr-TR.md' },
|
||
];
|
||
|
||
const MAX_REGULAR_PROMPTS_TO_DISPLAY = 120;
|
||
|
||
/**
|
||
* Convert locale to URL language prefix
|
||
* en -> en-US, zh -> zh-CN, others remain unchanged
|
||
*/
|
||
function getLocalePrefix(locale: string): string {
|
||
if (locale === 'en') {
|
||
return 'en-US';
|
||
}
|
||
if (locale === 'zh') {
|
||
return 'zh-CN';
|
||
}
|
||
// Other language codes (e.g., zh-TW, ja-JP) remain unchanged
|
||
return locale;
|
||
}
|
||
|
||
/**
|
||
* 清理提示词内容中的代码块标记
|
||
* 移除 ``` 或 ```json 等格式的代码块标记
|
||
*
|
||
* 处理的情况:
|
||
* - ``` 提示词 ```
|
||
* - ```json 提示词 ```
|
||
* - ```python 提示词 ``` 等任意语言标识符
|
||
* - 多行内容中的代码块标记
|
||
*/
|
||
function cleanPromptContent(content: string): string {
|
||
if (!content) return content;
|
||
|
||
let cleaned = content;
|
||
|
||
// 匹配代码块标记:``` 或 ```language(如 ```json, ```python 等)
|
||
// 语言标识符可能包含字母、数字、连字符等(如 json, python, typescript)
|
||
|
||
// 1. 移除开头的代码块标记
|
||
// 匹配:``` + 可选语言标识符 + 可选空白字符 + 可选换行
|
||
cleaned = cleaned.replace(/^```[\w-]*\s*\n?/im, '');
|
||
|
||
// 2. 移除结尾的代码块标记
|
||
// 匹配:可选换行 + ``` + 可选空白字符
|
||
cleaned = cleaned.replace(/\n?```\s*$/im, '');
|
||
|
||
// 3. 移除中间可能存在的代码块标记(处理嵌套或错误格式的情况)
|
||
// 匹配:换行 + ``` + 可选语言标识符 + 可选空白 + 换行
|
||
cleaned = cleaned.replace(/\n```[\w-]*\s*\n/g, '\n');
|
||
|
||
// 4. 清理首尾空白字符(包括换行)
|
||
cleaned = cleaned.trim();
|
||
|
||
return cleaned;
|
||
}
|
||
|
||
export function generateMarkdown(data: SortedPrompts, total: number, locale: string = 'en'): string {
|
||
const { featured, regular, stats, categories } = data;
|
||
|
||
const displayedRegular = regular.slice(0, MAX_REGULAR_PROMPTS_TO_DISPLAY);
|
||
const hiddenCount = total - displayedRegular.length;
|
||
|
||
let md = generateHeader(locale);
|
||
md += generateLanguageNavigation(locale);
|
||
md += generateGalleryCTA(categories || [], locale);
|
||
md += generateTOC(locale);
|
||
md += generateWhatIs(locale);
|
||
md += generateStats(stats, locale);
|
||
md += generateFeaturedSection(featured, locale);
|
||
md += generateAllPromptsSection(displayedRegular, hiddenCount, locale);
|
||
md += generateContribute(locale);
|
||
md += generateFooter(locale);
|
||
|
||
return md;
|
||
}
|
||
|
||
function generateHeader(locale: string): string {
|
||
const arenaUrl = `https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts`;
|
||
return `
|
||
> 💡 ${t('seedancePromo', locale)}
|
||
# 🚀 ${t('title', locale)}
|
||
|
||
[](https://github.com/sindresorhus/awesome)
|
||
[](https://github.com/YouMind-OpenLab/awesome-gpt-image-2)
|
||
[](https://creativecommons.org/licenses/by/4.0/)
|
||
[](https://github.com/YouMind-OpenLab/awesome-gpt-image-2/actions)
|
||
[](docs/CONTRIBUTING.md)
|
||
|
||
> 🎨 ${t('subtitle', locale)}
|
||
|
||
> ⚠️ ${t('copyright', locale)}
|
||
|
||
---
|
||
|
||
`;
|
||
}
|
||
|
||
function generateLanguageNavigation(currentLocale: string): string {
|
||
let md = '';
|
||
|
||
// Sort languages so current one is first or en is first?
|
||
// Keeping the array order is usually best, but we want a clean list.
|
||
|
||
const badges = SUPPORTED_LANGUAGES.map(lang => {
|
||
const isCurrent = lang.code === currentLocale || (currentLocale.startsWith(lang.code) && !SUPPORTED_LANGUAGES.some(l => l.code === currentLocale && l.code !== lang.code));
|
||
// Color logic: green for current, blue for others, or grey?
|
||
// Using the style from the image: "Click to View"
|
||
|
||
const color = isCurrent ? 'brightgreen' : 'lightgrey';
|
||
const text = isCurrent ? 'Current' : 'Click%20to%20View';
|
||
const link = lang.readmeFileName;
|
||
|
||
// If current, maybe no link or link to self?
|
||
// Using shields.io badge format: label-message-color
|
||
// Label = Native Name, Message = Click to View (or Ver Traducción etc)
|
||
|
||
const safeName = encodeURIComponent(lang.name);
|
||
|
||
return `[](${link})`;
|
||
});
|
||
|
||
md += badges.join(' ') + '\n\n---\n\n';
|
||
return md;
|
||
}
|
||
|
||
function generateGalleryCTA(categories: FilterCategory[], locale: string): string {
|
||
// 根据语言选择图片:zh 和 zh-TW 使用 zh,其他使用 en
|
||
const imageLang = locale === 'zh' || locale === 'zh-TW' ? 'zh' : 'en';
|
||
const coverImage = `public/images/gpt-image-2-prompts-cover-${imageLang}.png`;
|
||
|
||
let md = `## 🌐 ${t('viewInGallery', locale)}
|
||
|
||
<div align="center">
|
||
|
||

|
||
|
||
</div>
|
||
|
||
**[${t('browseGallery', locale)}](https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts)**
|
||
|
||
${t('galleryFeatures', locale)}
|
||
|
||
| Feature | ${t('githubReadme', locale)} | ${t('youmindGallery', locale)} |
|
||
|---------|--------------|---------------------|
|
||
| 🎨 ${t('visualLayout', locale)} | ${t('linearList', locale)} | ${t('masonryGrid', locale)} |
|
||
| 🔍 ${t('search', locale)} | ${t('ctrlFOnly', locale)} | ${t('fullTextSearch', locale)} |
|
||
| 🤖 ${t('aiGenerate', locale)} | - | ${t('aiOneClickGen', locale)} |
|
||
| 📱 ${t('mobile', locale)} | ${t('basic', locale)} | ${t('fullyResponsive', locale)} |
|
||
| 🏷️ ${t('categories', locale)} | - | ${t('categoryBrowsing', locale)} |
|
||
|
||
`;
|
||
|
||
// Add categories section if available
|
||
if (categories.length > 0) {
|
||
md += generateCategoriesSection(categories, locale);
|
||
}
|
||
|
||
md += `---
|
||
|
||
`;
|
||
|
||
return md;
|
||
}
|
||
|
||
function generateCategoriesSection(categories: FilterCategory[], locale: string): string {
|
||
// Get parent categories (no parentId)
|
||
const parentCategories = categories.filter(c => c.parentId === null);
|
||
|
||
let md = `\n### 🏷️ ${t('browseByCategory', locale)}\n\n`;
|
||
|
||
for (const parent of parentCategories) {
|
||
// Parent category - no link
|
||
md += `- **${parent.title}**\n`;
|
||
|
||
// Get children of this parent
|
||
const children = categories.filter(c => c.parentId === parent.id);
|
||
for (const child of children) {
|
||
// Child category - with link
|
||
const categoryUrl = `https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts?categories=${child.slug}`;
|
||
md += ` - [${child.title}](${categoryUrl})\n`;
|
||
}
|
||
}
|
||
|
||
md += `\n`;
|
||
return md;
|
||
}
|
||
|
||
function generatePromptSection(prompt: Prompt, index: number, locale: string): string {
|
||
const authorLink = prompt.author.link || '#';
|
||
const publishedDate = new Date(prompt.sourcePublishedAt).toLocaleDateString(locale, {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric',
|
||
});
|
||
|
||
// Use translatedContent if available, otherwise fallback to content
|
||
// Clean code block markers (``` or ```json etc.) from the content
|
||
const rawContent = prompt.translatedContent || prompt.content;
|
||
const promptContent = cleanPromptContent(rawContent);
|
||
const hasArguments = promptContent.includes('{argument');
|
||
|
||
let md = `### No. ${index + 1}: ${prompt.title}\n\n`;
|
||
|
||
// Language badge
|
||
md += `}-blue)\n`;
|
||
|
||
if (prompt.featured) {
|
||
md += `\n`;
|
||
}
|
||
|
||
if (hasArguments) {
|
||
md += `\n`;
|
||
}
|
||
|
||
md += `\n#### 📖 ${t('description', locale)}\n\n${prompt.description}\n\n`;
|
||
md += `#### 📝 ${t('prompt', locale)}\n\n\`\`\`\n${promptContent}\n\`\`\`\n\n`;
|
||
|
||
if (prompt.sourceMedia && prompt.sourceMedia.length > 0) {
|
||
md += `#### 🖼️ ${t('generatedImages', locale)}\n\n`;
|
||
|
||
prompt.sourceMedia.forEach((imageUrl, imgIndex) => {
|
||
md += `##### Image ${imgIndex + 1}\n\n`;
|
||
md += `<div align="center">\n`;
|
||
md += `<img src="${imageUrl}" width="${prompt.featured ? '700' : '600'}" alt="${prompt.title} - Image ${imgIndex + 1}">\n`;
|
||
md += `</div>\n\n`;
|
||
});
|
||
}
|
||
|
||
md += `#### 📌 ${t('details', locale)}\n\n`;
|
||
md += `- **${t('author', locale)}:** [${prompt.author.name}](${authorLink})\n`;
|
||
md += `- **${t('source', locale)}:** [Twitter Post](${prompt.sourceLink})\n`;
|
||
md += `- **${t('published', locale)}:** ${publishedDate}\n`;
|
||
md += `- **${t('languages', locale)}:** ${prompt.language}\n\n`;
|
||
|
||
md += `**[${t('tryItNow', locale)}](https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts?id=${prompt.id})**\n\n`;
|
||
|
||
md += `---\n\n`;
|
||
|
||
return md;
|
||
}
|
||
|
||
function generateFeaturedSection(featured: Prompt[], locale: string): string {
|
||
if (featured.length === 0) return '';
|
||
|
||
let md = `## 🔥 ${t('featuredPrompts', locale)}\n\n`;
|
||
md += `> ⭐ ${t('handPicked', locale)}\n\n`;
|
||
|
||
featured.forEach((prompt, index) => {
|
||
md += generatePromptSection(prompt, index, locale);
|
||
});
|
||
|
||
return md;
|
||
}
|
||
|
||
function generateAllPromptsSection(regular: Prompt[], hiddenCount: number, locale: string): string {
|
||
if (regular.length === 0 && hiddenCount === 0) return '';
|
||
|
||
let md = `## 📋 ${t('allPrompts', locale)}\n\n`;
|
||
md += `> 📝 ${t('sortedByDate', locale)}\n\n`;
|
||
|
||
regular.forEach((prompt, index) => {
|
||
md += generatePromptSection(prompt, index, locale);
|
||
});
|
||
|
||
if (hiddenCount > 0) {
|
||
md += `---\n\n`;
|
||
md += `## 📚 ${t('morePrompts', locale)}\n\n`;
|
||
md += `<div align="center">\n\n`;
|
||
md += `### 🎯 ${hiddenCount} ${t('morePromptsDesc', locale)}\n\n`;
|
||
md += `Due to GitHub's content length limitations, we can only display the first ${MAX_REGULAR_PROMPTS_TO_DISPLAY} regular prompts in this README.\n\n`;
|
||
md += `**[${t('viewAll', locale)}](https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts)**\n\n`;
|
||
md += `The gallery features:\n\n`;
|
||
md += `${t('galleryFeature1', locale)}\n\n`;
|
||
md += `${t('galleryFeature2', locale)}\n\n`;
|
||
md += `${t('galleryFeature3', locale)}\n\n`;
|
||
md += `${t('galleryFeature4', locale)}\n\n`;
|
||
md += `</div>\n\n`;
|
||
md += `---\n\n`;
|
||
}
|
||
|
||
return md;
|
||
}
|
||
|
||
function generateStats(stats: { total: number; featured: number }, locale: string): string {
|
||
const now = new Date().toLocaleString(locale, {
|
||
timeZone: 'UTC',
|
||
dateStyle: 'full',
|
||
timeStyle: 'long',
|
||
});
|
||
|
||
return `## 📊 ${t('stats', locale)}
|
||
|
||
<div align="center">
|
||
|
||
| ${t('metric', locale)} | ${t('count', locale)} |
|
||
|--------|-------|
|
||
| 📝 ${t('totalPrompts', locale)} | **${stats.total}** |
|
||
| ⭐ ${t('featured', locale)} | **${stats.featured}** |
|
||
| 🔄 ${t('lastUpdated', locale)} | **${now}** |
|
||
|
||
</div>
|
||
|
||
---
|
||
|
||
`;
|
||
}
|
||
|
||
function generateTOC(locale: string): string {
|
||
// Generating anchors is tricky with i18n, but GitHub usually slugifies the headers.
|
||
// For now we assume English anchors or standard GitHub behavior.
|
||
// Ideally we should use the exact translation for the link text, and the slugified translation for the href.
|
||
// But simple manual mapping for now.
|
||
|
||
return `## 📖 ${t('toc', locale)}
|
||
|
||
- [🌐 ${t('viewInGallery', locale)}](#-view-in-web-gallery)
|
||
- [🤔 ${t('whatIs', locale)}](#-what-is-gpt-image-2)
|
||
- [📊 ${t('stats', locale)}](#-statistics)
|
||
- [🔥 ${t('featuredPrompts', locale)}](#-featured-prompts)
|
||
- [📋 ${t('allPrompts', locale)}](#-all-prompts)
|
||
- [🤝 ${t('howToContribute', locale)}](#-how-to-contribute)
|
||
- [📄 ${t('license', locale)}](#-license)
|
||
- [🙏 ${t('acknowledgements', locale)}](#-acknowledgements)
|
||
- [⭐ ${t('starHistory', locale)}](#-star-history)
|
||
|
||
---
|
||
|
||
`;
|
||
}
|
||
|
||
function generateWhatIs(locale: string): string {
|
||
return `## 🤔 ${t('whatIs', locale)}
|
||
|
||
${t('whatIsIntro', locale)}
|
||
|
||
- 🎯 ${t('multimodalUnderstanding', locale)}
|
||
- 🎨 ${t('highQualityGeneration', locale)}
|
||
- ⚡ ${t('fastIteration', locale)}
|
||
- 🌈 ${t('diverseStyles', locale)}
|
||
- 🔧 ${t('preciseControl', locale)}
|
||
- 📐 ${t('complexScenes', locale)}
|
||
|
||
📚 ${t('learnMore', locale)}
|
||
|
||
### 🚀 ${t('raycastIntegration', locale)}
|
||
|
||
${t('raycastDescription', locale)}
|
||
|
||
**${t('example', locale)}**
|
||
\`\`\`
|
||
${t('raycastExample', locale)}
|
||
\`\`\`
|
||
|
||
${t('raycastUsage', locale)}
|
||
|
||
---
|
||
|
||
`;
|
||
}
|
||
|
||
function generateContribute(locale: string): string {
|
||
return `## 🤝 ${t('howToContribute', locale)}
|
||
|
||
${t('welcomeContributions', locale)}
|
||
|
||
### 🐛 ${t('githubIssue', locale)}
|
||
|
||
1. Click [**${t('submitNewPrompt', locale)}**](https://github.com/YouMind-OpenLab/awesome-gpt-image-2/issues/new?template=submit-prompt.yml)
|
||
2. ${t('fillForm', locale)}
|
||
3. ${t('submitWait', locale)}
|
||
4. ${t('approvedSync', locale)}
|
||
5. ${t('appearInReadme', locale)}
|
||
|
||
**${t('note', locale)}** ${t('noteContent', locale)}
|
||
|
||
${t('seeContributing', locale)}
|
||
|
||
---
|
||
|
||
`;
|
||
}
|
||
|
||
function generateFooter(locale: string): string {
|
||
const timestamp = new Date().toISOString();
|
||
|
||
return `## 📄 ${t('license', locale)}
|
||
|
||
${t('licensedUnder', locale)}
|
||
|
||
---
|
||
|
||
## 🙏 ${t('acknowledgements', locale)}
|
||
|
||
- [Payload CMS](https://payloadcms.com/)
|
||
- [youmind.com](https://youmind.com)
|
||
|
||
---
|
||
|
||
## ⭐ ${t('starHistory', locale)}
|
||
|
||
[](https://star-history.com/#YouMind-OpenLab/awesome-gpt-image-2&Date)
|
||
|
||
---
|
||
|
||
<div align="center">
|
||
|
||
**[🌐 ${t('viewInGallery', locale)}](https://youmind.com/${getLocalePrefix(locale)}/gpt-image-2-prompts)** •
|
||
**[📝 ${t('submitPrompt', locale)}](https://github.com/YouMind-OpenLab/awesome-gpt-image-2/issues/new?template=submit-prompt.yml)** •
|
||
**[⭐ ${t('starRepo', locale)}](https://github.com/YouMind-OpenLab/awesome-gpt-image-2)**
|
||
|
||
<sub>🤖 ${t('autoGenerated', locale)} ${timestamp}</sub>
|
||
|
||
</div>
|
||
`;
|
||
}
|