Texterify - An Open Source Localization Resource Management System

Introduction⌗
I’ve been exploring ways to manage localization resource files for a while. Previously, our team tried Weblate, but we couldn’t fully adopt it mainly because of its high learning curve and unfriendly interaction for non-technical translators!
Recently, while browsing GitHub, I accidentally discovered an open source project called Texterify with only three-digit stars. The official website provides SaaS services, and after trying it out, I found it quite good in various aspects - it really resonated with me!
Overview⌗
Texterify’s backend is developed using the Ruby on Rails framework and supports the following language file types:
- iOS: .strings files
- JSON: .json files
- JSON POEditor: .json files
- JSON FormatJS: .json files
- Chrome JSON: .json files
- go-i18n: .toml files
- TOML: .toml files
- Java: .properties files
- gettext: .po files
- Django: .po files
- Flutter: .arb files
- XLIFF: .xlf, .xliff files
- Rails: .yml, .yaml files
- YAML: .yml, .yaml files
Although it’s open source, the Self-Hosted version still requires purchasing a license, otherwise many features won’t be available. Its license authentication uses the Gitlab-License library, so the authorization issue can be solved technically.
Deployment⌗
Following the official documentation, I used Docker Compose for a demo deployment, which went quite smoothly. However, if you want to customize some configurations, there are still some areas for improvement in the project. I’ve already submitted Issues and PRs on GitHub.
# Clone the docker-compose configuration.
git clone https://github.com/texterify/texterify-docker-compose-setup.git
cd texterify-docker-compose-setup
# Generate a secret key for the app.
# Make sure to keep this private.
echo SECRET_KEY_BASE=`openssl rand -hex 64` > secrets.env
# Open the .env file and replace "example.com" with your host (if you
# are trying to run Texterify locally just use "localhost" as host).
# Also make sure to check out the other configuration options (see below).
# Start the service.
docker volume create --name=texterify-database
docker volume create --name=texterify-assets
docker compose up --always-recreate-deps
# After everything has started create the database in another terminal.
docker compose exec app bin/rails db:create db:migrate db:seed
The issues I encountered are as follows:
- The first issue is that the
sidekiq
service cannot get theREDIS_URL
environment variable. I submitted an Issue. - The second issue is that the
FORM
in theSMTP
configuration cannot be highly customized. I submitted a PR.
Pricing Model⌗
After running the demo locally, I found that many features require upgrading the plan, similar to the Gitlab CE model. For specific plans, you can refer to the official website.
So I analyzed Texterify’s source code and found that the license authorization mainly uses the Gitlab::License package.
After a quick look, I discovered that this authorization mode uses asymmetric encryption to encrypt license information, and then uses a public key for decryption to obtain the license information!
Cracking⌗
Knowing the authorization principle of the license, the next step became easier. I realized that I just needed to replace the license_key.pub
file in the Docker Image to validate a self-generated license.
Prepare a script file license.rb
for generating the license:
require "openssl"
require "gitlab/license"
key_pair = OpenSSL::PKey::RSA.generate(2048)
File.open("license_key", "w") { |f| f.write(key_pair.to_pem) }
public_key = key_pair.public_key
File.open("license_key.pub", "w") { |f| f.write(public_key.to_pem) }
private_key = OpenSSL::PKey::RSA.new File.read("license_key")
Gitlab::License.encryption_key = private_key
license = Gitlab::License.new
license.licensee = {
"name" => "George",
"email" => "[email protected]",
"company" => "Betterde Inc.",
}
license.starts_at = Date.new(2020, 1, 1) # Start date
license.expires_at = Date.new(2050, 1, 1) # End date
license.notify_admins_at = Date.new(2049, 12, 1)
license.notify_users_at = Date.new(2049, 12, 1)
license.block_changes_at = Date.new(2050, 1, 1)
license.restrictions = {
plan: "business", active_users_count: 3000
}
puts "License:"
puts license
data = license.export
puts "Exported license:"
puts data
File.open("texterify.texterify-license", "w") { |f| f.write(data) }
public_key = OpenSSL::PKey::RSA.new File.read("license_key.pub")
Gitlab::License.encryption_key = public_key
data = File.read("texterify.texterify-license")
$license = Gitlab::License.import(data)
puts "Imported license:"
puts $license
unless $license
raise "The license is invalid."
end
if $license.restricted?(:active_user_count)
active_user_count = 10000
if active_user_count > $license.restrictions[:active_user_count]
raise "The active user count exceeds the allowed amount!"
end
end
if $license.notify_admins?
puts "The license is due to expire on #{$license.expires_at}."
end
if $license.notify_users?
puts "The license is due to expire on #{$license.expires_at}."
end
module Gitlab
class GitAccess
def check(cmd, changes = nil)
if $license.block_changes?
return build_status_object(false, "License expired")
end
end
end
end
puts "This instance of Texterify Enterprise Edition is licensed to:"
$license.licensee.each do |key, value|
puts "#{key}: #{value}"
end
if $license.expired?
puts "The license expired on #{$license.expires_at}"
elsif $license.will_expire?
puts "The license will expire on #{$license.expires_at}"
else
puts "The license will never expire."
end
The main parts that need to be replaced are the information in licensee
, the expiration time, and most importantly, license.restrictions
. Since we’re going all out, let’s set the plan to business
, and active_users_count
is used to limit the number of users!
Then execute the following command on macOS to generate the license:
gem install gitlab-license
ruby license.rb
ls -la
total 32
drwxr-xr-x 6 George staff 192 Mar 9 15:36 .
drwx------@ 30 George staff 960 Mar 9 15:36 ..
-rw-r--r--@ 1 George staff 2168 Mar 8 15:57 license.rb
-rw-r--r-- 1 George staff 1679 Mar 8 15:57 license_key
-rw-r--r-- 1 George staff 451 Mar 8 15:57 license_key.pub
-rw-r--r-- 1 George staff 1489 Mar 8 15:57 texterify.texterify-license
Then log in to Texterify’s admin dashboard and upload the license:
Since I didn’t enter the
active_users_count
information, it’s not displayed here!
I hope this is helpful, Happy hacking…