• ¶

    Translation Plugin

    Last Revised: April 23, 2015 by Ben Gotow

    TranslateButton is a simple React component that allows you to select a language from a popup menu and translates draft text into that language.

    request = require 'request'
    
    {Utils,
     React,
     ComponentRegistry,
     DraftStore} = require 'nylas-exports'
    {Menu,
     RetinaImg,
     Popover} = require 'nylas-component-kit'
    
    YandexTranslationURL = 'https://translate.yandex.net/api/v1.5/tr.json/translate'
    YandexTranslationKey = 'trnsl.1.1.20150415T044616Z.24814c314120d022.0a339e2bc2d2337461a98d5ec9863fc46e42735e'
    YandexLanguages =
      'English': 'en'
      'Spanish': 'es'
      'Russian': 'ru'
      'Chinese': 'zh'
      'French': 'fr'
      'German': 'de'
      'Italian': 'it'
      'Japanese': 'ja'
      'Portuguese': 'pt'
      'Korean': 'ko'
    
    class TranslateButton extends React.Component
  • ¶

    Adding a displayName makes debugging React easier

      @displayName: 'TranslateButton'
  • ¶

    Since our button is being injected into the Composer Footer, we receive the local id of the current draft as a prop (a read-only property). Since our code depends on this prop, we mark it as a requirement.

      @propTypes:
        draftLocalId: React.PropTypes.string.isRequired
  • ¶

    The render method returns a React Virtual DOM element. This code looks like HTML, but don’t be fooled. The CJSX preprocessor converts

    <a href="http://facebook.github.io/react/">Hello!</a>

    into Javascript objects which describe the HTML you want:

    React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')

    We’re rendering a Popover with a Menu inside. These components are part of N1’s standard nylas-component-kit library, and make it easy to build interfaces that match the rest of N1’s UI.

      render: =>
        React.createElement(Popover, {"ref": "popover",  \
                 "className": "translate-language-picker pull-right",  \
                 "buttonComponent": (@_renderButton())},
          React.createElement(Menu, {"items": ( Object.keys(YandexLanguages) ),  \
                "itemKey": ( (item) -> item ),  \
                "itemContent": ( (item) -> item ),  \
                "onSelect": (@_onTranslate)
                })
        )
    
    
  • ¶

    Helper method to render the button that will activate the popover. Using the RetinaImg component makes it easy to display an image from our package. RetinaImg will automatically chose the best image format for our display.

      _renderButton: =>
        React.createElement("button", {"className": "btn btn-toolbar"}, """
          Translate
    """, React.createElement(RetinaImg, {"name": "toolbar-chevron.png"})
        )
    
      _onTranslate: (lang) =>
        @refs.popover.close()
  • ¶

    Obtain the session for the current draft. The draft session provides us the draft object and also manages saving changes to the local cache and Nilas API as multiple parts of the application touch the draft.

        session = DraftStore.sessionForLocalId(@props.draftLocalId)
        session.prepare().then =>
          body = session.draft().body
          bodyQuoteStart = Utils.quotedTextIndex(body)
  • ¶

    Identify the text we want to translate. We need to make sure we don’t translate quoted text.

          if bodyQuoteStart > 0
            text = body.substr(0, bodyQuoteStart)
          else
            text = body
    
          query =
            key: YandexTranslationKey
            lang: YandexLanguages[lang]
            text: text
            format: 'html'
  • ¶

    Use Node’s request library to perform the translation using the Yandex API.

          request {url: YandexTranslationURL, qs: query}, (error, resp, data) =>
            return @_onError(error) unless resp.statusCode is 200
            json = JSON.parse(data)
  • ¶

    The new text of the draft is our translated response, plus any quoted text that we didn’t process.

            translated = json.text.join('')
            translated += body.substr(bodyQuoteStart) if bodyQuoteStart > 0
  • ¶

    To update the draft, we add the new body to it’s session. The session object automatically marshalls changes to the database and ensures that others accessing the same draft are notified of changes.

            session.changes.add(body: translated)
            session.changes.commit()
    
      _onError: (error) =>
        @refs.popover.close()
        dialog = require('remote').require('dialog')
        dialog.showErrorBox('Geolocation Failed', error.toString())
    
    
    module.exports =
  • ¶

    Activate is called when the package is loaded. If your package previously saved state using serialize it is provided.

      activate: (@state) ->
        ComponentRegistry.register TranslateButton,
          role: 'Composer:ActionButton'
  • ¶

    Serialize is called when your package is about to be unmounted. You can return a state object that will be passed back to your package when it is re-activated.

      serialize: ->
  • ¶

    This optional method is called when the window is shutting down, or when your package is being updated or disabled. If your package is watching any files, holding external resources, providing commands or subscribing to events, release them here.

      deactivate: ->
        ComponentRegistry.unregister(TranslateButton)