Hızlı Başlangıç

Bu makalede, bir Crowdin Uygulaması oluşturmanın temel ilkeleri hakkında bilgi bulacaksınız. Sonuç olarak, yol boyunca kendi uygulamanızı oluşturabileceksiniz. Bunu başarmak için Node.js ve Heroku platformunu kullanacaksınız.

Önkoşullar:

Kurulum

Bu adımda, ayrıntılı olarak gözden geçirmek için örnek uygulama kodunu yerel makinenize indirmeniz gerekir.

Uygulama kodunu GitHub’dan indirin ve aşağıdaki komutları kullanarak gerekli tüm bağımlılıkları yükleyin:

git clone https://github.com/crowdin/apps-quick-start.git
cd apps-quick-start
npm i

Bu noktada, aşağıdaki yapıya sahip çalışan bir uygulamanız var:

  • public – Ortak varlıkları (örn. resimler, stiller, yazı tipleri vb.) içeren dizin.
  • resources/views – HTML sayfa şablonlarını içeren dizin.
  • app.json – Heroku uygulamasını ve nasıl çalıştırılacağını anlatan Heroku platformu için tanımlayıcı dosya.
  • manifest.js – Crowdin uygulamasının kendisini ve temel yapısını anlatan uygulamanızın tanımlayıcısı.
  • index.js – Uygulamanın ana dosyası ve giriş noktası.
  • router.js – Yönlendiricinin başlatılmasından ve uygulamanın birincil uç noktalarından sorumlu dosya.
  • package.json – Uygulama ve kütüphaneleri hakkında bilgiler içeren dosya.

Artık uygulama Heroku’da dağıtım için hazır. Manifest.json dosyasında fark edebileceğiniz gibi, uygulama şu anki durumunda sadece proje menüsü modülünü içerir ve yetkilendirme gerektirmez, yani uygulamanın API’ye erişimi olmayacaktır.

Crowdin Uygulaması Dağıtma

Bu adımda uygulamayı dağıtacak ve Crowdin hesabınıza yükleyeceksiniz.

İlk olarak, Heroku platformunda bir uygulama oluşturun. Bunu aşağıdaki komutla yapabilirsiniz:

$ heroku create [crowdin-uygulama-adı]

Uygulama adı isteğe bağlıdır. Eğer bunu belirtmezseniz, Heroku sizin için otomatik olarak yapacaktır.

Uygulama oluşturulduktan sonra, Heroku CLI aşağıdaki çıktıyla yanıt verir:

Creating app... done, ⬢ crowdin-uygulama-adı
https://crowdin-uygulama-adı.herokuapp.com/ | https://git.heroku.com/crowdin-uygulama-adı.git

Yukarıdaki yanıtta görebileceğiniz gibi, Heroku başarılı olarak yeni bir uygulama oluşturdu ve URL’yi uygulamanın kendisine ve depolanacağı depoya verdi. Ayrıca Heroku, oluşturulan depoyu yerel Git’inize otomatik olarak bağlar.

Uygulama URL’sini kopyalayın ve aşağıdaki komutu kullanarak Heroku ortam değişkenlerinize ekleyin (<crowdin-app-name> dizgisini uygulamanızın adıyla değiştirdiğinizden emin olun):

heroku config:set BASE_URL=https://<crowdin-app-name>.herokuapp.com

Artık tüm hazırlıklar tamamlandı ve uygulamayı dağıtabilir ve aşağıdaki komutlarla çalıştırabilirsiniz:

git push heroku main
heroku ps:scale web=1
heroku open

Bu uygulama artık küresel olarak kullanılabilir ve Crowdin hesabına yüklenebilir. heroku open komutunu çalıştırdıktan sonra, dağıtılan uygulamayı varsayılan tarayıcınızda otomatik olarak açacaktır. Açılan sayfada, bildiri URL’sine sahip bir form göreceksiniz. Bu URL’yi kopyalayın ve elle kurulumu kullanarak uygulamayı Crowdin hesabınıza yükleyin. Kurulumdan sonra varolan projelerinizden birine gidin veya yeni bir tane oluşturun. Proje sekmeleri arasında “Başlarken” adlı yeni bir sekme göreceksiniz. Eğer uygulamayı açarken bir hoş geldiniz iletisi görürseniz, başarılı olarak yüklenmiştir.

Bu aşamada, uygulama çok fazla özellik sunmaz. Sadece yüklü olduğu proje hakkında bağlamsal bilgi alabilir. Proje hakkında bilgi almak için AP.getContext() düğmesine tıklayın. Daha karmaşık uygulamalar geliştirmek için API’ye erişmeniz gerekecek. Artık Crowdin Hesap Ayarları > Crowdin Uygulamaları’na gidebilir ve uygulamayı kaldırabilirsiniz.

Crowdin Uygulamasına API Erişimi Ekleme

Bu adımda, uygulamanın API’ye erişmesini sağlayabilirsiniz. Crowdin ve Crowdin Enterprise API ile çalışmak için bir OAuth uygulamasına ihtiyacınız var.

Bir OAuth uygulaması oluşturmak için şu adımları izleyin:

  1. Hesap Ayarları’nızı açın ve OAuth Uygulamaları sekmesine gidin.
  2. Yeni uygulama’ya tıklayın.
  3. Görünen ileti öğesinde, Ad alanında Başlarken öğesini belirtin.
  4. Yetkilendirme geri arama URL’leri alanında http://localhost’u belirtin.
  5. Tüm kapsamlar’ı seçin.
  6. Oluştur’a tıklayın.

Artık OAuth uygulama tablosunda yeni bir girişiniz var. Çift tıklama ile açın. Görünen ileti öğesinde, uygulamanın çalışması için gereken İstemci Kimliği ve İstemci gizli anahtarı bilgilerini göreceksiniz. OAuth uygulamasına ek olarak, Crowdin’den alınan API belirtecini depolamak için bir veritabanına ihtiyacınız var. Bunu başarmak için konsolda aşağıdaki komutu çalıştırın:

heroku addons:create heroku-postgresql:hobby-dev

Ayrıca, aşağıdaki komutları çalıştırırken aşağıdaki ortam değişkenlerini uygulamanıza ekleyin (yer tutucuları ve oluşturduğunuz OAuth uygulamasındaki verilerle değiştirdiğinizden emin olun):

heroku config:set AUTH_URL=https://accounts.crowdin.com/oauth/token
heroku config:set CLIENT_ID=<OAuth App Client ID>
heroku config:set CLIENT_SECRET=<OAuth App Client Secret>

Şimdi yerel projenize dönün ve manifest.js dosyasını açın. Bu dosyada, authorization ve events düğümlerini aşağıdaki kodla değiştirin:

authentication: {
  type: "crowdin_app",
  clientId: process.env.CLIENT_ID
},
events: {
  installed: "/installed",
  uninstall: "/uninstall",
},

Bu değişikliklerden sonra uygulama kurulumu sırasında Crowdin, bildiride belirtilen olaylar için yetkilendirme kodunu uygulamaya aktarmaya çalışacak. Bu yüzden, erişim belirtecini daha sonra kullanmak üzere kaydetmek için bu yolları eklemeniz ve veritabanını bağlamanız gerekir.

Şimdi veritabanına bağlanmak için bir db.js dosyası oluşturun ve aşağıdaki listelenen kodu ekleyin:

const Sequelize = require("sequelize");

const sequelize = new Sequelize(process.env.DATABASE_URL,
  {
    dialectOptions: {
      ssl: {
        require: true,
        rejectUnauthorized: false
      }
    }
  }
);

sequelize
  .authenticate()
  .then(async () => {
    console.log("Bağlantı başarılı olarak kuruldu.");
  })
  .catch(err => {
    console.error("Veritabanına bağlanılamıyor:", err);
  });

const Organization = sequelize.define("organization", {
  domain: {
    type: Sequelize.TEXT,
    allowNull: true
  },
  organizationId: {
    type: Sequelize.INTEGER
  },
  appId: {
    type: Sequelize.TEXT
  },
  addSecret: {
    type: Sequelize.TEXT
  },
  userId: {
    type: Sequelize.INTEGER
  },
  baseUrl: {
    type: Sequelize.TEXT
  },
  accessToken: {
    type: Sequelize.TEXT
  },
  accessTokenExpires: {
    type: Sequelize.INTEGER
  },
});

module.exports = {
  sequelize,
  Organization
}

Listelenen kodda, alınan verileri içinde depolamak için bir Kuruluş modelinin yanı sıra veritabanına bir bağlantı oluşturursunuz.

index.js dosyasını açın ve uygulama ile başlatılabilmesi için veritabanını bağlayın. Bunun için app.listen(…) işlevini aşağıdaki kodla sarın:

const { sequelize } = require("./db");

sequelize.sync().then(
  () => app.listen(port, () => console.log(`Dinlendiği bağlantı noktası ${ port }`))
);

Artık olayları Crowdin’den okuyacak yolları oluşturmaya başlayabilirsiniz. router.js dosyasını açın ve değişken dışa aktarma işleminden önce dosyaya aşağıdaki yolları ekleyin:

const { Organization } = require("./db");
const { default: axios } = require("axios");

router.post("/installed", async (req, res) => {
  const oauthPayload = {
    grant_type: "crowdin_app",
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    app_id: req.body.appId,
    app_secret: req.body.appSecret,
    domain: req.body.domain,
    user_id: req.body.userId,
  };

  const token = await axios.post(process.env.AUTH_URL, oauthPayload);
  const params = {
    domain: req.body.domain,
    organizationId: req.body.organizationId,
    appId: req.body.appId,
    appSecret: req.body.appSecret,
    userId: req.body.userId,
    baseUrl: req.body.baseUrl,
    accessToken: token.data.access_token,
    accessTokenExpires: Math.round(new Date().getTime() / 1000) + token.data.expires_in
  };

  const organization = await Organization.findOne({
    where: {
      domain: req.body.domain,
      organizationId: req.body.organizationId
    }
  });

  if (!organization) {
    await Organization.create(params);
  } else {
    organization.update(params);
  }

  res.status(200).send();
});

router.post("/uninstall", (req, res) => {
  Organization.destroy({
    where: {
      domain: req.body.domain,
      organizationId: +req.body.organizationId,
    }
  });

  res.status(200).send();
});

Installed olayı için bir istek aldıktan sonra, OAuth Yetkilendirme Kodu akışını kullanarak uygulama için bir yetkilendirme kodu alabilirsiniz. Yani bu akışa göre erişim belirtecini çıkarmak için bir yük oluşturmanız ve Axios kütüphanesini kullanarak bir istekte bulunmanız ve alınan verileri Kuruluş modeline kaydetmeniz veya bu modeldeki verileri güncellemeniz gerekir. Uninstall olayı için modelde kayıtlı giriş silinecektir.

Uygulama artık API ile çalışacağından ve projedeki bilgilere erişebileceğinden, uygulamanın yetkisiz kullanıcılarının Crowdin hesap verilerine erişmemesi için üçüncü taraf erişiminden korunmak zorundadır. Ara katman yazılımını modülün route.js dosyasındaki proje menüsüne ekleyerek yapabilirsiniz.

route.js dosyasını açın ve ara katman yazılımını ekleyin:

const jwt = require("jsonwebtoken");

const authorizeUser = (req, res, next) => {
  let decodedJwt = null;
  let authorizationHeader = req.header("Authorization");

  const token = authorizationHeader ? authorizationHeader.split(" ")[1] : req.query.jwtToken;

  if (req.query.jwtToken) {
    try {
      decodedJwt = jwt.verify(token, process.env.CLIENT_SECRET);
    } catch (error) {
      console.error(error);
      // can't decode or verify JWT
    }
  }

  res.locals.isAuthorized = decodedJwt && decodedJwt.sub
  res.locals.jwt = decodedJwt || {};

  next();
};

Modül sayfasını açarken Crowdin, istekte, uygulamayı açan kullanıcı hakkında bilgi içeren OAuth İstemci Gizli Anahtarı tarafından imzalanmış bir JWT belirteci gönderir. Buna dayanarak, ara katman yazılımı uygulamaya erişimi doğrulayacaktır.

Proje menüsü modül yolunu aşağıdaki şekilde değiştirin:

router.get("/proje-menüsü/", authorizeUser, (req, res) => res.render("proje-menüsü", { isAuthorized: res.locals.isAuthorized }));

Ayrıca, kullanıcı API aracılığıyla yetkilendirilmişse, depolanan erişim belirtecini kullanarak şu anki kullanıcı hakkında bilgiyı çıakrmak için bir yol ekleyin. Önce, apiClient.js dosyasını oluşturun ve aşağıda listelenen kodu ekleyin:

const { default: axios } = require("axios");

const getActiveAccessToken = async (organization) => {
  if (organization.accessTokenExpires > Math.round(new Date().getTime() / 1000)) {
    return organization.accessToken;
  }

  const oauthPayload = {
    grant_type: "crowdin_app",
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    app_id: organization.appId,
    app_secret: organization.appSecret,
    domain: organization.domain,
    user_id: organization.userId,
  };

  const response = await axios.post(process.env.AUTH_URL, oauthPayload);

  organization = await organization.update({
    accessToken: response.data.access_token,
    accessTokenExpires: Math.round(new Date().getTime() / 1000) + response.data.expires_in
  });

  return organization.accessToken;
};

module.exports = async (organization) => {
  const apiClient = axios.create({
    baseURL: `${ organization.baseUrl }/api/v2/`,
  });

  apiClient.interceptors.request.use(async (config) => {
    config.headers = {
      ...config.headers,
      Authorization: `Bearer ${ await getActiveAccessToken(organization) }`,
    };

    return config;
  });

  return apiClient;
}

Yukarıdaki kod, API’ye erişmek için şu anki erişim belirtecini çıkarmanızı sağlar.

Eğer belirtecin süresi dolmuşsa, yenileme belirtecine dayanarak şu anki yeni bir belirteç çıkarılır ve Kuruluş modelinde kaydedilir. Ayrıca, şu anki kuruluş için yetkilendirme başlığına sahip bir API istemcisini içeren bir işlev dışa aktarılır.

Şimdi route.js dosyasını açın ve kullanıcı hakkında bilgi almak için eylemi ekleyin:

const apiClient = require("./apiClient");

router.get("/user", authorizeUser, async (req, res) => {
  if (!res.locals.isAuthorized) {
    return res.status(403).send({ error: { message: "Kullanıcı yetkili değil" } });
  }

  const organization = await Organization.findOne({
    where: {
      domain: res.locals.jwt.domain,
      organizationId: res.locals.jwt.context.organization_id
    }
  });

  if (!organization) {
    return res.status(404).send({ error: { message: "Kuruluş bulunamadı" } });
  }

  try {
    const client = await apiClient(organization);
    const response = await client.get("user");

    return res.status(200).json(response.data || {});
  } catch (e) {
    console.log(e);

    return res.status(500).send({
      error: {
        message: "Bilinmeyen bir hata meydana geldi"
      }
    });
  }
});

Uygulama artık API ile çalışmaya hazırdır. Böylece değişiklikleri dağıtabilir ve uygulamanın yeni sürümünü Crowdin’e yükleyebilirsiniz. Değişiklikleri uygulamak için aşağıdaki komutları çalıştırın:

git add .
git commit -m "Crowdin’e API erişimi eklendi"
git push heroku main
heroku ps:scale web=1
heroku open

Daha önce olduğu gibi, bildiri URL’sini kopyalayın ve elle kurulumu kullanarak uygulamayı Crowdin hesabınıza yükleyin.

Yüklendikten sonra uygulamayı proje sekmesinde açın. Kullanıcı artık yetkili olduğundan, uygulama için daha fazla olanağımız var. Kullanıcı hakkında bilgi almak için Kullanıcı ayrıntılarını göster’e tıklayın.

Bir sonraki adımda, özel dosya biçimlerini işleme yeteneği ekleyeceksiniz. Şimdilik Crowdin Hesap Ayarları > Crowdin Uygulamaları’na gidebilir ve uygulamayı kaldırabilirsiniz.

Crowdin Uygulamasına Özel Dosya Biçimi Modülü Ekleme

Bu adımda, uygulamanıza bir JSON dosyasını özel olarak işleme ve bunun için bir önizleme oluşturma olasılığını ekleyeceksiniz.

manifest.js dosyasını açın ve aşağıdaki kodu modules düğümüne ekleyerek yeni bir modül türü ekleyin:

{
  "özel-dosya-biçimi": [
    {
      "key": "özel-dosya-biçimi",
      "type": "özel-dosya-biçimi",
      "url": "/process",
      "signaturePatterns": {
        "fileName": ".+\\.json$",
        "fileContent": "\"merhaba_dunya\":"
      }
    }
  ]
}

Yukarıdaki kodla, içinde “merhaba_dunya” içeren JSON biçiminde dosyayı işleyecek özel-dosya-biçimi anahtarına sahip bir modül ekleyebilirsiniz. Eğer Crowdin projesine böyle bir dosya yüklerseniz, özel ayrıştırma için yol işlemine gönderilecektir.

Proje kök dizininde fileHelper.js dosyasını oluşturun ve aşağıdaki listelenen kodu buna ekleyin:

const fs = require("fs");
let ejs = require("ejs");
const { v4: uuidv4 } = require("uuid");
const got = require("got");
const Blob = require("node-blob");

const MAX_BODY_SIZE = 5 * 1024 * 1024;  // 5mb

async function parseFile(req) {
  const fileContent = await getContent(req.file);

  let isTranslation = false;

  if (req.targetLanguages.length && req.targetLanguages[0].id) {
    isTranslation = true;
  }

  const sourceStrings = [];
  const previewStrings = [];

  let previewIndex = 0;

  if (fileContent[Object.keys(fileContent)[0]]) {
    for (const key in fileContent) {
      if (typeof fileContent[key] !== "string") {
        continue;
      }

      let translations = {};

      if (isTranslation) {
        const languageId = req.targetLanguages[0].id;
        translations = { [languageId]: { text: fileContent[key] } }
      }

      sourceStrings.push({
        identifier: key,
        context: `Bazı bağlamlar: \n ${ fileContent[key] }`,
        customData: "",
        previewId: previewIndex,
        labels: [],
        isHidden: false,
        text: fileContent[key],
        translations: translations
      });

      previewStrings[key] = {
        text: fileContent[key],
        id: previewIndex
      };

      previewIndex++;
    }
  }

  let previewHtml = "";

  try {
    const previewEjs = fs.readFileSync("resources/views/file-preview.ejs", "utf8");

    let ejsTemplate = ejs.compile(previewEjs);

    previewHtml = ejsTemplate({
      fileName: req.file.name,
      strings: previewStrings
    });
  } catch (err) {
console.error(err);
  }

  if (new Blob([JSON.stringify(sourceStrings)]).size < MAX_BODY_SIZE) {
    return {
      data: {
        strings: sourceStrings,
        preview:Buffer.from(previewHtml).toString("base64")
      }
    }
  }

  return {
    data: {
      stringsUrl: getDownloadUrl(JSON.stringify(sourceStrings)),
      previewUrl: getDownloadUrl(previewHtml)
    }
  }
}

async function buildFile(req) {
  const fileContent = await getContent(req.file);

  const languageId = req.targetLanguages[0].id;

  const translations = await getStringsForExport(req);

  if (!fileContent[Object.keys(fileContent)[0]]) {
    throw "Çevirecek bir şey yok";
  }

  for (const key of Object.keys(fileContent)) {
    if (typeof fileContent[key] !== "string") {
      continue;
    }

    fileContent[key] = getTranslation(translations, key, languageId, fileContent[key]);
  }

  const responseContent = JSON.stringify(fileContent, null, 2);

  if (new Blob([responseContent]).size < MAX_BODY_SIZE) {
    return {
      data: {
        content:Buffer.from(responseContent).toString("base64")
      }
    }
  }

  return {
    data: {
      contentUrl: getDownloadUrl(responseContent)
    }
  }
}

function getDownloadUrl(fileContents) {
  const tmpFileName = uuidv4();

  fs.writeFileSync("/tmp/" + tmpFileName, fileContents);

  return `${process.env.BASE_URL }/download?file=` + tmpFileName;
}

async function getContent(file) {
  if (file.content) {
    return JSON.parse(Buffer.from(file.content, "base64").toString());
  }

  return (await got(file.contentUrl, {json: true})).body;
}

function getTranslation(translations, stringId, languageId, fallbackTranslation) {
  for (let i = 0; i < translations.length; i++) {
    if (translations[i].identifier === stringId) {
      return translations[i].translations[languageId].text;
    }
  }

  return fallbackTranslation;
}

async function getStringsForExport(req) {
  if (!req.strings&& !req.stringsUrl) {
    throw "Hatalı yük alındı: Bulunan dizgiler yok";
  }

  if (req.strings) {
    return req.strings;
  }

  return (await got(req.stringsUrl, { json: true })).body;
}

module.exports = {
  parseFile,
  buildFile
};

Yukarıdaki listelenen kod, bir dosyayla çalışmak için iki ana yöntem içerir: parseFile ve buildFile.

  • parseFile – Kaynak dosyanın içeriğinin işlenmesini, kaynak dosyadan çeviri için dizgilerin çıkarılmasını ve kaynak dosya için bir HTML önizlemesinin oluşturulmasını sağlar, böylece çevirmenler içeriği Crowdin düzenleyicisinde rahatlıkla gözden geçirebilir ve çevirebilir.
  • buildFile – Crowdin’e aktarılan dizgileri kullanarak orijinal özel biçimde dışa aktarmak için bir dosya oluşturmayı sağlar.

Şimdi route.js dosyasını açın ve Crowdin’den içerik alacak yeni bir yol oluşturun:

const { parseFile, buildFile } = require("./fileHelper");

router.post("/process", authorizeUser, async (req, res) => {
  let response
  const request = req.body;

  try {
    switch (request.jobType) {
      case "parse-file":
        response = await parseFile(request);
        break;
      case "build-file":
        response = await buildFile(request);
        break;
    }

    res.status(200).send(response);
  } catch (e) {
console.error(e);

    res.status(500).send({
      error: {
        message: "Bilinmeyen hata meydana geldi"
      }
    });
  }
});

router.get("/download", authorizeUser, (req, res) => {
  res.download(`/tmp/${ req.query.file }`, "dosya.txt", (err) => {
    if (err) {
console.log("İndirme hatası: ", err);
    } else {
console.log("İndirme iyi gitti");
    }

    fs.unlinkSync("/tmp/" + req.query.file);
  });
});

/process yolunun yanı sıra, boyutu 5 MB’ı aşan dosyayı indirmek için bir yol da eklemeniz gerekir. Artık uygulama özel bir JSON dosyasını işleyebilir.

Değişiklikleri dağıtmak için aşağıdaki komutları çalıştırın:

git add .
git commit -m "Özel dosya biçimi modülü eklendi"
git push heroku main
heroku ps:scale web=1
heroku open

Daha önce olduğu gibi, bildiri URL’sini kopyalayın ve elle kurulumu kullanarak uygulamayı bir kez daha Crowdin hesabınıza yükleyin. Ardından yerel makinenizde bir JSON dosyası oluşturun ve aşağıdaki içeriği ekleyin:

{
    "hello_world": "Merhaba Dünya!",
    "test": "Bu, çeviri için örnek bir dizgidir"
}

Bu, çeviri için Crowdin projenize yükleyeceğiniz kaynak dosyasıdır. Şimdi deneme projenizi Crowdin’de açın ve henüz oluşturulan JSON kaynak dosyasını ona yükleyin. Dosya yüklendikten ve içe aktarıldıktan sonra Crowdin, çeviri için iki dizgi içerdiğini görüntüleyecek. Giriş sekmesine gidin, hedef dillerden birini seçin ve dosyaya tıklayın. Düzenleyicinin sol panelinde bu JSON dosyası için özel bir önizleme göreceksiniz.

Bu makale yararlı oldu mu?