diff --git a/app.rb b/app.rb index fde1dd0..a73725e 100644 --- a/app.rb +++ b/app.rb @@ -4,7 +4,7 @@ require 'yaml' # Configure Sinatra set :port, ENV['PORT'] || 4567 -set :bind, '0.0.0.0' +set :bind, '::' # Load responses from YAML file RESPONSES = YAML.load_file('responses.yml') @@ -31,26 +31,64 @@ end def determine_language(params, headers) # Priority: URL parameter > Accept-Language header > default return params[:lang] if params[:lang] && RESPONSES[params[:lang]] - + # Parse Accept-Language header if headers['HTTP_ACCEPT_LANGUAGE'] accepted_langs = headers['HTTP_ACCEPT_LANGUAGE'] .split(',') .map { |lang| lang.split(';').first.downcase.strip } - + # Check for exact matches first accepted_langs.each do |lang| return lang if RESPONSES[lang] end - + # Check for language family matches (e.g., 'nb' or 'nn' -> 'no') accepted_langs.each do |lang| case lang + when /^zh/ # Chinese variants + return 'zh' + when /^es/ # Spanish variants + return 'es' + when /^hi/ # Hindi variants + return 'hi' + when /^ar/ # Arabic variants + return 'ar' + when /^bn/ # Bengali variants + return 'bn' + when /^pt/ # Portuguese variants + return 'pt' + when /^ru/ # Russian variants + return 'ru' + when /^ja/ # Japanese variants + return 'ja' + when /^tr/ # Turkish variants + return 'tr' + when /^ko/ # Korean variants + return 'ko' + when /^fr/ # French variants + return 'fr' + when /^de/ # German variants + return 'de' + when /^vi/ # Vietnamese variants + return 'vi' + when /^it/ # Italian variants + return 'it' + when /^th/ # Thai variants + return 'th' + when /^pl/ # Polish variants + return 'pl' + when /^nl/ # Dutch variants + return 'nl' + when /^ur/ # Urdu variants + return 'ur' + when /^ms/ # Malay variants + return 'ms' when /^nb|nn/ # Norwegian Bokmål/Nynorsk return 'no' when /^sv/ # Swedish variants return 'sv' - when /^da/ # Danish variants + when /^da/ # Danish variants return 'da' when /^is/ # Icelandic variants return 'is' @@ -63,7 +101,7 @@ def determine_language(params, headers) end end end - + DEFAULT_LANGUAGE end @@ -71,20 +109,20 @@ end get '/' do lang = determine_language(params, request.env) no_response = get_no_response(lang) - + case request.accept.first&.to_s when /application\/json/ content_type :json - { - answer: no_response, + { + answer: no_response, language: lang, language_name: get_language_name(lang), - timestamp: Time.now.iso8601 + timestamp: Time.now.iso8601 }.to_json else content_type :html - erb :index, locals: { - no_response: no_response, + erb :index, locals: { + no_response: no_response, language: lang, language_name: get_language_name(lang), available_languages: get_available_languages @@ -95,20 +133,20 @@ end # Languages endpoint - list available languages get '/languages' do content_type :json - { + { languages: get_available_languages, default: DEFAULT_LANGUAGE, - timestamp: Time.now.iso8601 + timestamp: Time.now.iso8601 }.to_json end # Explicit JSON endpoint with language support get '/api/no' do lang = determine_language(params, request.env) - + content_type :json - { - answer: get_no_response(lang), + { + answer: get_no_response(lang), language: lang, language_name: get_language_name(lang), timestamp: Time.now.iso8601, @@ -120,21 +158,21 @@ end # Language-specific endpoint get '/api/no/:lang' do lang = params[:lang] - + unless RESPONSES[lang] content_type :json status 404 - return { - error: "Language not supported", + return { + error: "Language not supported", requested: lang, available: get_available_languages.map { |l| l[:code] }, - timestamp: Time.now.iso8601 + timestamp: Time.now.iso8601 }.to_json end - + content_type :json - { - answer: get_no_response(lang), + { + answer: get_no_response(lang), language: lang, language_name: get_language_name(lang), timestamp: Time.now.iso8601, @@ -146,10 +184,10 @@ end # Health check endpoint get '/health' do content_type :json - { - status: "healthy", + { + status: "healthy", languages_loaded: RESPONSES.keys.size, - timestamp: Time.now.iso8601 + timestamp: Time.now.iso8601 }.to_json end @@ -157,20 +195,20 @@ end get '/*' do lang = determine_language(params, request.env) no_response = get_no_response(lang) - + case request.accept.first&.to_s when /application\/json/ content_type :json - { - answer: no_response, + { + answer: no_response, language: lang, language_name: get_language_name(lang), - timestamp: Time.now.iso8601 + timestamp: Time.now.iso8601 }.to_json else content_type :html - erb :index, locals: { - no_response: no_response, + erb :index, locals: { + no_response: no_response, language: lang, language_name: get_language_name(lang), available_languages: get_available_languages @@ -193,7 +231,7 @@ __END__ padding: 0; box-sizing: border-box; } - + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); @@ -203,7 +241,7 @@ __END__ justify-content: center; color: white; } - + .container { text-align: center; max-width: 700px; @@ -214,19 +252,19 @@ __END__ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.2); } - + h1 { font-size: 3rem; margin-bottom: 1rem; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } - + .subtitle { font-size: 1.2rem; margin-bottom: 1rem; opacity: 0.9; } - + .language-info { font-size: 1rem; margin-bottom: 2rem; @@ -235,7 +273,41 @@ __END__ border-radius: 10px; display: inline-block; } - + + .logo { + width: 120px; + height: 120px; + margin: 0 auto 2rem auto; + background: radial-gradient(circle at 30% 30%, #ff6b6b, #ee5a52, #dc3545); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 4rem; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); + box-shadow: 0 8px 32px rgba(255, 107, 107, 0.4); + border: 3px solid rgba(255, 255, 255, 0.3); + position: relative; + } + + .logo::before { + content: ''; + position: absolute; + top: -3px; + left: -3px; + right: -3px; + bottom: -3px; + background: conic-gradient(from 0deg, #ff6b6b, #ff8e8e, #ff6b6b); + border-radius: 50%; + z-index: -1; + animation: rotate 3s linear infinite; + } + + @keyframes rotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + .no-response { font-size: 4rem; font-weight: bold; @@ -248,7 +320,7 @@ __END__ border: 2px solid rgba(255, 107, 107, 0.3); word-break: break-word; } - + .controls { margin: 2rem 0; display: flex; @@ -257,7 +329,7 @@ __END__ justify-content: center; align-items: center; } - + .refresh-btn { background: linear-gradient(45deg, #ff6b6b, #ff8e8e); color: white; @@ -269,12 +341,12 @@ __END__ transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3); } - + .refresh-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4); } - + .language-selector { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); @@ -284,12 +356,12 @@ __END__ font-size: 1rem; cursor: pointer; } - + .language-selector option { background: #444; color: white; } - + .api-info { margin-top: 2rem; padding: 1rem; @@ -297,34 +369,34 @@ __END__ border-radius: 10px; font-size: 0.9rem; } - + .api-info code { background: rgba(0, 0, 0, 0.2); padding: 0.2rem 0.5rem; border-radius: 4px; font-family: 'Courier New', monospace; } - + .nordic-flag { display: inline-block; margin-left: 0.5rem; font-size: 1.2em; } - + @media (max-width: 600px) { .container { margin: 1rem; padding: 1.5rem; } - + h1 { font-size: 2rem; } - + .no-response { font-size: 2.5rem; } - + .controls { flex-direction: column; gap: 0.5rem; @@ -336,7 +408,7 @@ __END__
The definitive multilingual API for negative responses
- +JSON API: GET /api/no or GET /api/no/:lang
Health Check: GET /health