Low commit activity in last 3 years
A long-lived project that still receives updates
Exposes the ThatLanguage language-detection library over HTTP as eight JSON endpoints. Packaged as a gem rather than an application; the deployment supplies the web server.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 0
~> 2.2
~> 8.0
~> 13.0
~> 3.13
~> 2.0
~> 1.88

Runtime

 Project Readme

CI

ThatLanguage::Service

A thin Sinatra wrapper that exposes the ThatLanguage language-detection library over HTTP as eight JSON endpoints.

This is a library gem, not an application: it ships a config.ru but declares no web server, so the deployment picks its own.

Installation

Add it to your Gemfile:

gem "that_language-service"

Requires Ruby 3.1 or later. The that_language core gem is pulled in automatically; no git source or extra declaration is needed.

Running it

$ bundle install
$ bundle exec puma config.ru -p 9292

Then:

$ curl --get --data-urlencode 'text=Hallo Welt' http://localhost:9292/detect
{"language":"German","language_code":"de","confidence":0.5}

Endpoints

Every endpoint answers both GET and POST, takes the input in a text parameter, and responds with Content-Type: application/json.

Endpoint Returns
/language {"language": "German"}
/language_code {"language_code": "de"}
/detect {"language": …, "language_code": …, "confidence": …}
/details the full ranked result set, best match first
/available {"available": {"de": "German", …}}
/available_languages list of language names
/available_language_codes list of language codes
/version {"version": …, "core_version": …} — this service, and the core library behind it

/details returns every candidate language with its scoring breakdown:

{"results":[{"language":"German","language_code":"de","confidence":0.5,
             "value":1.0,"hit_ratio":0.5,"hit_count":1,"words_count":2}]}

Configuration

The wordlist tier is a property of the core library, not of this service. Configure it before the application is mounted:

ThatLanguage.configure do |config|
  config.wordlist_path = File.join("wordlists", "100k")
end

Warm the cache at boot. The wordlists are loaded lazily on the first request, which costs about a second and a few hundred megabytes. Without a warm-up the first user pays for it:

ThatLanguage.language_code("Hello world!")  # in config.ru, before `run`

Cross-origin requests

Rack::Protection::JsonCsrf is deliberately disabled — it answers 403 to any request carrying a cross-origin Referer, which would block the intended browser use case while guarding data that is already public. CORS headers are not set here; the deployment adds them.

Development

$ bundle install
$ bundle exec rspec       # 161 examples, 0 failures
$ bundle exec rubocop
$ bin/contract            # this service vs the core gem

Requires Ruby >= 3.1; developed and pinned against 4.0.6 (see .tool-versions).

.rspec sets --fail-fast, so a run that stops early reads as though examples disappeared. If the count is below 161, look for a failure before concluding the suite shrank.

The contract test

The specs are rack-test against the app in-process. They assert what this app does, which means they cannot notice this app drifting from the core gem it exposes — the same blind spot that let the client's return types diverge from the core gem's for a decade while its suite stayed green.

bin/contract closes that gap. It starts a real service from the working tree and compares every endpoint against the core gem in a separate process:

Each endpoint's payload must equal the JSON projection of the core gem's return value — JSON.parse(body) == JSON.parse(<core value>.to_json).

Things worth knowing before you touch it:

  • The wire erases types. The core gem returns symbols for language names and codes; JSON has no symbols, so the symbol/string distinction cannot be asserted across HTTP. Sortedness, ranking and the /detect key set are therefore checked as explicit invariants — a diff alone proves the two sides agree, not that either is right. See contract/canonical.rb, which is where the reasoning lives.
  • /detect is a deliberate subset of Result#to_h: three keys, not seven. It is pinned in both directions, so returning all seven is drift too.
  • contract/gemfiles/service.gemfile resolves this gem from the working tree, not from rubygems. From rubygems it would test the last release and pass on a branch that breaks the projection.
  • The job goes red when the core gem changes, not only this repository. That is the point of a contract test, but it means a red badge here is not always a bug here.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Deradon/that_language-service. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

Available as open source under the terms of the MIT License.