Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ Use it in apps, bots, landing pages, Slack integrations, rejection letters, or w

---

## 🌐 Language Support

No-as-a-Service supports multiple languages based on the `Accept-Language` header:

### Example Request with Language Preference
```http
GET /no
Accept-Language: fr
```

### Currently Supported Languages
- English (en) - Default
- German (de)
- French (fr)

### Contributing Translations
We welcome contributions for new languages! Simply create a new JSON file in the `reasons` directory:
1. Name it using the 2-letter language code (e.g., `es.json` for Spanish)
2. Add your creative rejection phrases as a JSON array
3. Submit a pull request

## 🛠️ Self-Hosting

Want to run it yourself? It’s lightweight and simple.
Expand Down
54 changes: 51 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
const express = require('express');
const rateLimit = require('express-rate-limit');
const fs = require('fs');
const path = require('path');

const app = express();
app.set('trust proxy', true);
const PORT = process.env.PORT || 3000;

// Load reasons from JSON
const reasons = JSON.parse(fs.readFileSync('./reasons.json', 'utf-8'));
const availableLanguages = {
'en': 'en.json',
'de': 'de.json',
'fr': 'fr.json',
};

// Default language
const defaultLanguage = 'en';

// Load reasons based on language
function getReasons(lang) {
try {
const languageFile = availableLanguages[lang] || availableLanguages[defaultLanguage];
const filePath = path.join(__dirname, 'reasons', languageFile);

// Check if the file exists
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
}

// Fallback to English if file doesn't exist
return JSON.parse(fs.readFileSync(path.join(__dirname, 'reasons', availableLanguages[defaultLanguage]), 'utf-8'));
} catch (error) {
console.error(`Error loading language file: ${error}`);
// Return a single reason as emergency fallback
return ["Sorry, I can't do that right now."];
}
}

// Rate limiter: 120 requests per minute per IP
const limiter = rateLimit({
Expand All @@ -23,11 +50,32 @@ app.use(limiter);

// Random rejection reason endpoint
app.get('/no', (req, res) => {
// Parse Accept-Language header
const acceptLanguage = req.headers['accept-language'];
let lang = defaultLanguage;

if (acceptLanguage) {
// Extract language code (e.g., 'en-US' -> 'en')
const requestedLanguages = acceptLanguage.split(',')
.map(langQ => {
const [language, qValue] = langQ.trim().split(';');
return language.substring(0, 2).toLowerCase(); // Get primary language tag
});

// Find the first requested language that's available
lang = requestedLanguages.find(l => availableLanguages[l]) || defaultLanguage;
}

// Load reasons for the selected language
const reasons = getReasons(lang);

// Pick a random reason
const reason = reasons[Math.floor(Math.random() * reasons.length)];

res.json({ reason });
});

// Start server
app.listen(PORT, () => {
console.log(`No-as-a-Service is running on port ${PORT}`);
});
});
7 changes: 7 additions & 0 deletions reasons/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
"Tut mir leid, aber ich muss leider ablehnen.",
"Das passt jetzt leider gar nicht in meinen Zeitplan.",
"Die Sterne stehen heute nicht günstig dafür.",
"Ich habe bereits zu viele Projekte am Laufen.",
"Mein innerer Schweinehund lässt grüßen und sagt nein."
]
File renamed without changes.
7 changes: 7 additions & 0 deletions reasons/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
"Je dois respectueusement décliner.",
"C'est une idée intéressante, mais ce n'est pas pour moi.",
"Mon agenda est malheureusement déjà bien rempli.",
"Je dois écouter ma voix intérieure, et elle me dit non.",
"Je préfère réserver mon énergie pour d'autres projets."
]