Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Ore localizable #546

Merged
merged 6 commits into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package controllers
import java.util.{Base64, UUID}

import akka.http.scaladsl.model.Uri
import javax.inject.Inject
import controllers.sugar.Bakery
import db.ModelService
import db.impl.OrePostgresDriver.api._
Expand All @@ -22,7 +21,7 @@ import ore.rest.ProjectApiKeyTypes._
import ore.rest.{OreRestfulApi, OreWrites}
import ore.{OreConfig, OreEnv}
import play.api.cache.AsyncCacheApi
import play.api.i18n.MessagesApi
import play.api.i18n.{Lang, Messages, MessagesApi}
import util.StatusZ
import util.functional.{EitherT, OptionT, Id}
import util.instances.future._
Expand Down Expand Up @@ -173,7 +172,8 @@ final class ApiController @Inject()(api: OreRestfulApi,
}
}

private def error(key: String, error: String) = Json.obj("errors" -> Map(key -> List(this.messagesApi(error))))
private def error(key: String, error: String)(implicit messages: Messages) =
Json.obj("errors" -> Map(key -> List(messages(error))))

def deployVersion(version: String, pluginId: String, name: String): Action[AnyContent] = ProjectAction(pluginId).async { implicit request =>
version match {
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/OreBaseController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ abstract class OreBaseController(implicit val env: OreEnv,
implicit override val users: UserBase = this.service.getModelBase(classOf[UserBase])
implicit override val projects: ProjectBase = this.service.getModelBase(classOf[ProjectBase])
implicit override val organizations: OrganizationBase = this.service.getModelBase(classOf[OrganizationBase])
implicit val lang: Lang = Lang.defaultLang

override val signOns: ModelAccess[SignOn] = this.service.access[SignOn](classOf[SignOn])

override def notFound(implicit request: OreRequest[_]) = NotFound(views.html.errors.notFound())
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Organizations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Organizations @Inject()(forms: OreForms,
*/
def showCreator(): Action[AnyContent] = UserLock().async { implicit request =>
request.user.ownedOrganizations.size.map { size =>
if (size >= this.createLimit) Redirect(ShowHome).withError(this.messagesApi("error.org.createLimit", this.createLimit))
if (size >= this.createLimit) Redirect(ShowHome).withError(request.messages.apply("error.org.createLimit", this.createLimit))
else {
Ok(views.createOrganization())
}
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Reviews.scala
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ final class Reviews @Inject()(data: DataHelper,
userId = id,
originId = requestUser.id.get,
notificationType = NotificationTypes.VersionReviewed,
message = messagesApi("notification.project.reviewed", project.slug, version.versionString)
messageArgs = List("notification.project.reviewed", project.slug, version.versionString)
)
}
} map (notificationTable ++= _) flatMap (service.DB.db.run(_)) // Batch insert all notifications
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/Users.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Users @Inject()(fakeUser: FakeUser,
tagline <- bindFormEitherT[Future](this.forms.UserTagline)(_ => BadRequest)
} yield {
if (tagline.length > maxLen) {
Redirect(ShowUser(user)).flashing("error" -> this.messagesApi("error.tagline.tooLong", maxLen))
Redirect(ShowUser(user)).flashing("error" -> request.messages.apply("error.tagline.tooLong", maxLen))
} else {
user.setTagline(tagline)
Redirect(ShowUser(user))
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/project/Projects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ class Projects @Inject()(stats: StatTracker,
getProject(author, slug).map { project =>
this.projects.delete(project)
UserActionLogger.log(request, LoggedAction.ProjectVisibilityChange, project.id.getOrElse(-1), "null", project.visibility.nameKey)
Redirect(ShowHome).withSuccess(this.messagesApi("project.deleted", project.name))
Redirect(ShowHome).withSuccess(request.messages.apply("project.deleted", project.name))
}.merge
}
}
Expand All @@ -676,7 +676,7 @@ class Projects @Inject()(stats: StatTracker,
val comment = this.forms.NeedsChanges.bindFromRequest.get.trim
data.project.setVisibility(VisibilityTypes.SoftDelete, comment, request.user.id.get).map { _ =>
UserActionLogger.log(request.request, LoggedAction.ProjectVisibilityChange, data.project.id.getOrElse(-1), VisibilityTypes.SoftDelete.nameKey, data.project.visibility.nameKey)
Redirect(ShowHome).withSuccess(this.messagesApi("project.deleted", data.project.name))
Redirect(ShowHome).withSuccess(request.messages.apply("project.deleted", data.project.name))
}
}

Expand Down
3 changes: 2 additions & 1 deletion app/controllers/project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import ore.project.io.{DownloadTypes, InvalidPluginFileException, PluginFile, Pl
import ore.{OreConfig, OreEnv, StatTracker}
import play.api.Logger
import play.api.cache.AsyncCacheApi
import play.api.i18n.MessagesApi
import play.api.i18n.{Lang, MessagesApi}
import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent, Request, Result}
import play.filters.csrf.CSRF
Expand Down Expand Up @@ -518,6 +518,7 @@ class Versions @Inject()(stats: StatTracker,
ProjectAction(author, slug).async { request =>
val dlType = downloadType.flatMap(i => DownloadTypes.values.find(_.id == i)).getOrElse(DownloadTypes.UploadedFile)
implicit val r: OreRequest[AnyContent] = request.request
implicit val lang: Lang = request.lang
val project = request.data.project
getVersion(project, target)
.filterOrElse(v => !v.isReviewed, Redirect(ShowProject(author, slug)).withError("error.plugin.stateChanged"))
Expand Down
7 changes: 6 additions & 1 deletion app/controllers/sugar/Actions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ore.permission.{EditPages, EditSettings, HideProjects, Permission}
import play.api.cache.AsyncCacheApi
import play.api.mvc.Results.{Redirect, Unauthorized}
import play.api.mvc._
import play.api.i18n.Messages
import security.spauth.SingleSignOnConsumer
import slick.jdbc.JdbcBackend

Expand Down Expand Up @@ -203,7 +204,11 @@ trait Actions extends Calls with ActionHelpers {

def transform[A](request: Request[A]): Future[OreRequest[A]] = {
implicit val service: ModelService = users.service
HeaderData.of(request).map(new OreRequest(_, request))
HeaderData.of(request).map { data =>
val requestWithLang =
data.currentUser.flatMap(_.lang).fold(request)(lang => request.addAttr(Messages.Attrs.CurrentLang, lang))
new OreRequest(data, requestWithLang)
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions app/db/impl/OrePostgresDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ore.user.notification.NotificationTypes
import ore.user.notification.NotificationTypes.NotificationType
import slick.ast.BaseTypedType
import slick.jdbc.JdbcType
import play.api.i18n.Lang

/**
* Custom Postgres driver to support array data and custom type mappings.
Expand Down Expand Up @@ -55,6 +56,7 @@ trait OrePostgresDriver extends ExPostgresProfile with PgArraySupport with PgAgg
implicit val visibiltyTypeMapper : JdbcType[Visibility] with BaseTypedType[Visibility] = MappedJdbcType.base[Visibility, Int](_.id, VisibilityTypes.withId)
implicit val loggedActionMapper : JdbcType[LoggedAction] with BaseTypedType[LoggedAction] = MappedJdbcType.base[LoggedAction, Int](_.value, LoggedAction.withValue)
implicit val loggedActionContextMapper : JdbcType[LoggedActionContext] with BaseTypedType[LoggedActionContext] = MappedJdbcType.base[LoggedActionContext, Int](_.value, LoggedActionContext.withValue)
implicit val langTypeMapper : JdbcType[Lang] with BaseTypedType[Lang] = MappedJdbcType.base[Lang, String](_.toLocale.toLanguageTag, Lang.apply)
}

}
Expand Down
3 changes: 1 addition & 2 deletions app/db/impl/access/OrganizationBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class OrganizationBase(override val service: ModelService,
extends ModelBase[Organization] {

override val modelClass: Class[Organization] = classOf[Organization]
implicit val lang: Lang = Lang.defaultLang

val Logger = play.api.Logger("Organizations")

Expand Down Expand Up @@ -88,7 +87,7 @@ class OrganizationBase(override val service: ModelService,
user.sendNotification(Notification(
originId = org.id.get,
notificationType = NotificationTypes.OrganizationInvite,
message = this.messages("notification.organization.invite", role.roleType.title, org.username)
messageArgs = List("notification.organization.invite", role.roleType.title, org.username)
))
}
})
Expand Down
8 changes: 5 additions & 3 deletions app/db/impl/schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ore.project.io.DownloadTypes.DownloadType
import ore.rest.ProjectApiKeyTypes.ProjectApiKeyType
import ore.user.Prompts.Prompt
import ore.user.notification.NotificationTypes.NotificationType
import play.api.i18n.Lang

/*
* Database schema definitions. Changes must be first applied as an evolutions
Expand Down Expand Up @@ -234,9 +235,10 @@ class UserTable(tag: RowTag) extends ModelTable[User](tag, "users") with NameCol
def joinDate = column[Timestamp]("join_date")
def avatarUrl = column[String]("avatar_url")
def readPrompts = column[List[Prompt]]("read_prompts")
def lang = column[Lang]("language")

override def * = (id.?, createdAt.?, fullName.?, name, email.?, tagline.?, globalRoles, joinDate.?,
avatarUrl.?, readPrompts, pgpPubKey.?, lastPgpPubKeyUpdate.?, isLocked) <> ((User.apply _).tupled,
avatarUrl.?, readPrompts, pgpPubKey.?, lastPgpPubKeyUpdate.?, isLocked, lang.?) <> ((User.apply _).tupled,
User.unapply)

}
Expand Down Expand Up @@ -323,11 +325,11 @@ class NotificationTable(tag: RowTag) extends ModelTable[Notification](tag, "noti
def userId = column[Int]("user_id")
def originId = column[Int]("origin_id")
def notificationType = column[NotificationType]("notification_type")
def message = column[String]("message")
def messageArgs = column[List[String]]("message_args")
def action = column[String]("action")
def read = column[Boolean]("read")

override def * = (id.?, createdAt.?, userId, originId, notificationType, message, action.?,
override def * = (id.?, createdAt.?, userId, originId, notificationType, messageArgs, action.?,
read) <> (Notification.tupled, Notification.unapply)

}
Expand Down
2 changes: 2 additions & 0 deletions app/db/impl/table/ModelKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ore.Colors.Color
import ore.permission.role.RoleTypes.RoleType
import ore.project.Categories.Category
import ore.user.Prompts.Prompt
import play.api.i18n.Lang

/**
* Collection of String keys used for table bindings within Models.
Expand Down Expand Up @@ -63,6 +64,7 @@ object ModelKeys {
val JoinDate = new TimestampKey[User](_.joinDate, _.joinDate.orNull)
val AvatarUrl = new StringKey[User](_.avatarUrl, _.avatarUrl.orNull)
val ReadPrompts = new Key[User, List[Prompt]](_.readPrompts, _.readPrompts.toList)
val Language = new Key[User, Lang](_.lang, _.lang.orNull)

// Organization
val OrgOwnerId = new IntKey[Organization](_.userId, _.owner.userId)
Expand Down
9 changes: 4 additions & 5 deletions app/form/organization/OrganizationMembersUpdate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ case class OrganizationMembersUpdate(override val users: List[Int],
userUps: List[String],
roleUps: List[String]) extends TOrganizationRoleSetBuilder {

implicit val lang: Lang = Lang.defaultLang

//noinspection ComparingUnrelatedTypes
def saveTo(organization: Organization)(implicit cache: AsyncCacheApi, ex: ExecutionContext, messages: MessagesApi, users: UserBase): Unit = {
if (!organization.isDefined)
Expand All @@ -50,11 +48,12 @@ case class OrganizationMembersUpdate(override val users: List[Int],
for (role <- this.build()) {
val user = role.user
dossier.addRole(role.copy(organizationId = orgId))
user.flatMap {
_.sendNotification(Notification(
user.flatMap { user =>
import user.langOrDefault
user.sendNotification(Notification(
originId = orgId,
notificationType = NotificationTypes.OrganizationInvite,
message = messages("notification.organization.invite", role.roleType.title, organization.name)
messageArgs = List("notification.organization.invite", role.roleType.title, organization.name)
))
}
}
Expand Down
6 changes: 2 additions & 4 deletions app/mail/EmailFactory.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ final class EmailFactory @Inject()(override val messagesApi: MessagesApi,
val AccountUnlocked = "email.accountUnlock"

implicit val users: UserBase = this.service.getModelBase(classOf[UserBase])
implicit val lang : Lang = Lang.defaultLang

def create(user: User, id: String)(implicit request: OreRequest[_]): Email = {

Email(
import user.langOrDefault
Email(
recipient = user.email.get,
subject = this.messagesApi(s"$id.subject"),
content = views.html.utils.email(
Expand Down
5 changes: 3 additions & 2 deletions app/models/admin/Review.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import play.api.libs.functional.syntax._
import play.twirl.api.Html
import util.StringUtils
import play.api.libs.json._

import scala.concurrent.Future

import play.api.i18n.Messages


/**
* Represents an approval instance of [[Project]] [[Version]].
Expand Down Expand Up @@ -133,7 +134,7 @@ case class Review(override val id: Option[Int] = None,
* @param message
*/
case class Message(message: String, time: Long = System.currentTimeMillis(), action: String = "message") {
def getTime(implicit oreConfig: OreConfig): String = StringUtils.prettifyDateAndTime(new Timestamp(time))
def getTime(implicit messages: Messages): String = StringUtils.prettifyDateAndTime(new Timestamp(time))
def isTakeover(): Boolean = action.equalsIgnoreCase("takeover")
def isStop(): Boolean = action.equalsIgnoreCase("stop")
def render(implicit oreConfig: OreConfig): Html = Page.Render(message)
Expand Down
4 changes: 3 additions & 1 deletion app/models/project/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import slick.lifted
import slick.lifted.{Rep, TableQuery}
import scala.concurrent.{ExecutionContext, Future}

import play.api.i18n.Messages

/**
* Represents an Ore package.
*
Expand Down Expand Up @@ -690,7 +692,7 @@ case class Project(override val id: Option[Int] = None,
* @param message
*/
case class Note(message: String, user: Int, time: Long = System.currentTimeMillis()) {
def getTime(implicit oreConfig: OreConfig): String = StringUtils.prettifyDateAndTime(new Timestamp(time))
def getTime(implicit messages: Messages): String = StringUtils.prettifyDateAndTime(new Timestamp(time))
def render(implicit oreConfig: OreConfig): Html = Page.Render(message)
}

Expand Down
3 changes: 1 addition & 2 deletions app/models/project/ProjectSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ case class ProjectSettings(override val id: Option[Int] = None,
private var _forumSync: Boolean = true)
extends OreModel(id, createdAt) with ProjectOwned {

implicit val lang: Lang = Lang.defaultLang
override type M = ProjectSettings
override type T = ProjectSettingsTable

Expand Down Expand Up @@ -198,7 +197,7 @@ case class ProjectSettings(override val id: Option[Int] = None,
userId = role.userId,
originId = project.ownerId,
notificationType = NotificationTypes.ProjectInvite,
message = messages("notification.project.invite", role.roleType.title, project.name))
messageArgs = List("notification.project.invite", role.roleType.title, project.name))
}

service.DB.db.run(TableQuery[NotificationTable] ++= notifications) // Bulk insert Notifications
Expand Down
7 changes: 5 additions & 2 deletions app/models/user/Notification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import scala.concurrent.{ExecutionContext, Future}
* @param createdAt Instant of cretion
* @param userId ID of User this notification belongs to
* @param notificationType Type of notification
* @param message Message to display
* @param messageArgs The unlocalized message to display, with the
* parameters to use when localizing
* @param action Action to perform on click
* @param read True if notification has been read
*/
Expand All @@ -28,11 +29,13 @@ case class Notification(override val id: Option[Int] = None,
override val userId: Int = -1,
originId: Int,
notificationType: NotificationType,
message: String,
messageArgs: List[String],
action: Option[String] = None,
private var read: Boolean = false)
extends OreModel(id, createdAt)
with UserOwned {
//TODO: Would be neat to have a NonEmptyList to get around guarding against this
require(messageArgs.nonEmpty, "Notification created with no message arguments")

override type M = Notification
override type T = NotificationTable
Expand Down
26 changes: 25 additions & 1 deletion app/models/user/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import util.functional.OptionT
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.Breaks._

import play.api.i18n.Lang

/**
* Represents a Sponge user.
*
Expand All @@ -53,7 +55,8 @@ case class User(override val id: Option[Int] = None,
private var _readPrompts: List[Prompt] = List(),
private var _pgpPubKey: Option[String] = None,
private var _lastPgpPubKeyUpdate: Option[Timestamp] = None,
private var _isLocked: Boolean = false)
private var _isLocked: Boolean = false,
private var _lang: Option[Lang] = None)
extends OreModel(id, createdAt)
with UserOwned
with ScopeSubject
Expand Down Expand Up @@ -248,6 +251,26 @@ case class User(override val id: Option[Int] = None,
if (isDefined) update(Tagline)
}

/**
* Returns this user's current language.
*/
def lang: Option[Lang] = _lang

/**
* Returns this user's current language, or the default language if none
* was configured.
*/
implicit def langOrDefault: Lang = _lang.getOrElse(Lang.defaultLang)

/**
* Sets this user's language.
* @param lang The new language.
*/
def setLang(lang: Option[Lang]) = {
this._lang = lang
if(isDefined) update(Language)
}

/**
* Returns this user's global [[RoleType]]s.
*
Expand Down Expand Up @@ -385,6 +408,7 @@ case class User(override val id: Option[Int] = None,
if (user != null) {
this.setUsername(user.username)
this.setEmail(user.email)
this.setLang(user.lang)
user.avatarUrl.map { url =>
if (!url.startsWith("http")) {
val baseUrl = config.security.get[String]("api.url")
Expand Down
Loading